Что будет происходить когда программа Java запустится?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Жизненный цикл Java приложения: От запуска до завершения
Полный процесс выполнения программы
Когда ты пишешь java -jar myapp.jar, происходит целый ряд операций. Разберу этот процесс пошагово с уровня Java Machine до уровня твоего кода.
Этап 1: Загрузка и инициализация JVM
Шаг 1: Запуск JVM процесса
// Команда
java -Xmx1024m -Xms512m -jar myapp.jar
// Что происходит:
// 1. ОС создаёт новый процесс
// 2. JVM выделяет памяти (Xms - initial, Xmx - maximum)
// 3. JVM инициализирует внутренние структуры
Шаг 2: Поиск и загрузка jar файла
// JVM читает META-INF/MANIFEST.MF
// Manifest-Version: 1.0
// Main-Class: com.example.Application
// Class-Path: lib/*
// JVM находит класс Main-Class
Этап 2: Bootstrap ClassLoader
Шаг 1: Загрузка core классов JVM
// Bootstrap ClassLoader загружает:n
// - java.lang.Object (базовый класс всего)
// - java.lang.String
// - java.lang.System
// - Все классы из rt.jar (runtime library)
public class Main {
public static void main(String[] args) {
// Object уже загружен, иначе это не работало бы
}
}
Шаг 2: Инициализация System класса
// System.class загружается и выполняет статический инициализатор
public final class System {
static {
// Инициализация System.out, System.err, System.in
// Загрузка native методов
// Установка security manager
}
}
Этап 3: Application ClassLoader и загрузка твоего кода
Шаг 1: Загрузка Main класса
// JVM использует Application ClassLoader для загрузки
// com.example.Application из JAR файла
public class Application {
// Статические переменные инициализируются ДО выполнения
private static final Logger log = LoggerFactory.getLogger(Application.class);
private static int counter = 0;
// 1. Загрузится класс
// 2. Выполнится статический блок
// 3. Инициализируются статические переменные
static {
System.out.println("Статический блок выполнится ДО main");
counter = 100;
}
}
Шаг 2: Вызов main метода
public class Application {
public static void main(String[] args) {
System.out.println("main метод начал выполняться");
}
}
// На этом этапе:
// 1. Все статические блоки уже выполнены
// 2. System.out готов к использованию
// 3. args содержит аргументы командной строки
Этап 4: Выполнение main метода
Пример сложного приложения:
public class Application {
// 1. ПЕРВОЕ: статическая инициализация
private static final Logger log = LoggerFactory.getLogger(Application.class);
static {
System.out.println("1. Статический блок");
}
public static void main(String[] args) {
System.out.println("2. Main метод начал");
// 3. Создание объекта класса
Application app = new Application();
// 4. Вызов метода
app.run();
System.out.println("5. Main закончил");
}
public Application() {
System.out.println("3. Конструктор");
}
public void run() {
System.out.println("4. Метод run");
}
}
// Вывод при запуске:
// 1. Статический блок
// 2. Main метод начал
// 3. Конструктор
// 4. Метод run
// 5. Main закончил
Этап 5: Во время выполнения (Runtime)
Динамическая загрузка классов
public class RuntimeLoader {
public static void main(String[] args) throws Exception {
System.out.println("1. JVM запустилась");
// Класс DatabaseConnection ещё НЕ загружен
// Он загрузится только когда понадобится
if (args.length > 0 && args[0].equals("--with-db")) {
// 2. Только сейчас загружается DatabaseConnection
DatabaseConnection db = new DatabaseConnection();
db.connect();
}
System.out.println("3. Программа завершилась");
}
}
// Если запустить БЕЗ --with-db:
// DatabaseConnection никогда не загрузится
// ClassNotFoundException не будет
Garbage Collection во время работы
public class GCExample {
public static void main(String[] args) {
// JVM отслеживает объекты, которые не используются
String str1 = new String("Hello"); // Объект создан
str1 = null; // Объект становится недостижимым
// GC может удалить этот объект
// В современной JVM:
// - Young Generation GC часто (быстро)
// - Old Generation GC редко (долго)
// - G1GC балансирует паузы
}
}
Этап 6: Spring Boot приложение (более сложный пример)
@SpringBootApplication
public class Application {
private static final Logger log = LoggerFactory.getLogger(Application.class);
public static void main(String[] args) {
// 1. Spring Bootstrap
log.info("1. Запуск Spring");
SpringApplication app = new SpringApplication(Application.class);
ConfigurableApplicationContext context = app.run(args);
// 2. На этом этапе:
// - Все @Bean инициализированы
// - Все @Service, @Repository инициализированы
// - HTTP сервер запущен (Tomcat на 8080)
log.info("2. Приложение запущено, готово к запросам");
// 3. Основной поток ждёт (не завершается)
// Это позволяет background threads продолжать работу
}
}
// При запуске Spring:
// 1. Создаёт ApplicationContext
// 2. Сканирует classpath на @Component аннотации
// 3. Создаёт beans в правильном порядке (зависимости first)
// 4. Вызывает @PostConstruct методы
// 5. Запускает Tomcat/Jetty
// 6. Выполняет @EventListener(ContextRefreshedEvent.class) методы
Этап 7: Обработка исключений
UncaughtExceptionHandler
public class SafeApplication {
public static void main(String[] args) {
// Перехватываем необработанные исключения в main потоке
Thread.setDefaultUncaughtExceptionHandler((thread, exception) -> {
System.err.println("Необработанное исключение в потоке: " + thread.getName());
exception.printStackTrace();
// Логируем, отправляем alert, etc
// Приложение может продолжить работу или завершиться
});
// В других потоках нужно ловить исключения
new Thread(() -> {
try {
throw new RuntimeException("Ошибка в background потоке");
} catch (Exception e) {
// ЗДЕСЬ нужно перехватить, иначе поток просто умрёт
}
}).start();
}
}
Этап 8: Завершение программы
Нормальное завершение
public class GracefulShutdown {
public static void main(String[] args) {
System.out.println("Приложение работает");
// Регистрируем shutdown hook
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.out.println("Приложение завершается, очищаем ресурсы");
// Закрываем БД соединения
// Завершаем background потоки
// Сохраняем состояние
}));
// Ждём
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("main завершилась нормально");
// JVM проверяет, есть ли non-daemon потоки
// Если нет → JVM завершается
}
}
// Порядок завершения:
// 1. main метод возвращает значение
// 2. Выполняются Shutdown Hooks (в обратном порядке регистрации)
// 3. Если все потоки daemon → JVM завершается
// 4. JVM закрывает файловые дескрипторы
// 5. Процесс ОС завершается с exit code
Аварийное завершение
public class EmergencyShutdown {
public static void main(String[] args) {
// Нормальное завершение
System.exit(0); // exit code 0 = успешно
// Аварийное завершение
System.exit(1); // exit code 1 = ошибка
// Мгновенное завершение (без Shutdown Hooks)
Runtime.getRuntime().halt(1);
}
}
Полная диаграмма для Spring Boot приложения
public class CompleteLifecycle {
public static void main(String[] args) {
// PHASE 1: JVM Bootstrap
// - ОС запускает JVM процесс
// - Bootstrap ClassLoader загружает core классы
// - Инициализируется System класс
// PHASE 2: Application Bootstrap
// - Application ClassLoader загружает твой код
// - Выполняются статические блоки в Main классе
// PHASE 3: Spring Initialization
log.info("PHASE 3: Spring начинает инициализацию");
SpringApplication.run(Application.class, args);
// PHASE 4: Runtime
// - Приложение обрабатывает HTTP запросы
// - Background потоки работают
// - GC периодически чистит память
// - JVM отслеживает потоки
// PHASE 5: Graceful Shutdown (при Ctrl+C)
// - Shutdown hooks выполняются
// - Потоки завершаются
// - Ресурсы закрываются
// - JVM выходит
}
}
Практические следствия этого знания
1. Статические блоки выполняются при загрузке класса
// Экспенсивная операция в static блоке может замедлить запуск
static {
initializeDatabaseConnections(); // Будет выполнено сразу при загрузке
}
// Решение: ленивая инициализация
private static DatabaseConnections connections;
public static DatabaseConnections getConnections() {
if (connections == null) {
connections = new DatabaseConnections(); // Инициализация по требованию
}
return connections;
}
2. Shutdown hooks важны для production
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
log.info("Gracefully shutting down");
application.stop();
database.close();
kafkaProducer.close();
}));
3. Exception в main не означает crash приложения (в Spring)
// Spring обработает это и будет работать дальше
Thread thread = new Thread(() -> {
throw new RuntimeException("Boom!"); // Поток умрёт, приложение продолжит
});
thread.start();
Итоговый вывод
Основные этапы:
- JVM Bootstrap — инициализация виртуальной машины
- ClassLoading — загрузка классов (ленивая)
- Статическая инициализация — выполнение static блоков
- main метод — начало выполнения твоего кода
- Runtime — обработка запросов, GC, monitoring
- Graceful shutdown — cleanup и завершение
Это знание критично для отладки проблем с инициализацией, performance и lifecycle управления.