JVM 的生命周期
JVM 也有生老病死
和普通程序一样,JVM 也有自己的生命周期。它不是从你写代码的那一刻就存在的,而是从你运行 java 命令时诞生,随着程序结束或异常退出而消亡。
理解 JVM 的生命周期,有助于你理解程序启动和退出的过程,也能帮助你在排查问题时判断「是应用崩了还是 JVM 崩了」。
JVM 的启动
运行 java -jar app.jar,操作系统会启动一个进程,这个进程里跑的就是 JVM。
启动流程
用户执行 java 命令
│
▼
JVM 创建(装载 native 的 launcher 代码)
│
▼
创建堆内存、设置 GC 策略
│
▼
加载引导类(Bootstrap ClassLoader)
│
▼
执行 main() 方法 → Java 程序开始运行JVM 启动时,会经历以下步骤:
- 创建 JVM 实例:操作系统分配进程内存,JVM 加载 native 代码(负责初始化)
- 创建堆:根据
-Xmx、-Xms参数设置堆大小 - 选择 GC 策略:根据机器配置和 JVM 参数选择合适的垃圾回收器
- 加载引导类:Bootstrap ClassLoader 加载
java.lang.String、java.lang.Thread等核心类 - 执行 main 方法:JVM 创建一个主线程,调用
main(String[] args)
注意:main() 方法的执行才是 Java 程序的开始,而不是 java 命令执行的那一刻。java 命令到 main() 执行之间,JVM 做了大量的初始化工作。
public class LifeCycleDemo {
public static void main(String[] args) {
// 从这里开始,是 Java 程序真正运行的地方
System.out.println("Hello from main thread");
}
}main 线程的创建
当 JVM 执行 main 方法时,首先会创建一个主线程:
// JVM 内部大致等价于:
Thread mainThread = new Thread(() -> {
// 执行 main 方法的字节码
LifeCycleDemo.main(args);
});
mainThread.start();这个主线程和其他所有线程一样,有自己的 PC 寄存器、虚拟机栈。如果主线程结束了,java 进程不一定退出——如果有其他非守护线程还在运行,JVM 会继续存活。
运行阶段
Java 程序运行期间,JVM 负责:
- 加载类:按需加载
.class文件(懒加载) - 执行字节码:解释执行 + JIT 编译执行
- 内存管理:对象分配、垃圾回收
- 线程调度:多线程并发执行
- 异常处理:捕获和传播异常
这个阶段是 JVM 最「忙」的时候。
线程与 JVM 的关系
Java 程序可以创建任意多个线程,每个线程都会占用一定的资源(PC 寄存器、虚拟机栈)。
public class MultiThreadDemo {
public static void main(String[] args) {
// 创建一个新的工作线程
Thread worker = new Thread(() -> {
// 这个线程有自己独立的 PC 寄存器和虚拟机栈
System.out.println("Worker thread is running");
});
worker.start();
// 主线程继续执行
System.out.println("Main thread is running");
}
}所有用户线程都是 JVM 的子线程——JVM 自己也有内部的 GC 线程、编译器线程。它们共享堆和方法区,但各有独立的栈。
JVM 的退出
JVM 的退出条件有两个,满足其一即可:
1. 所有非守护线程结束
这是正常退出的方式。当 main 方法执行完毕,并且程序中创建的所有非守护线程都结束时,JVM 会正常退出。
public class ExitDemo {
public static void main(String[] args) throws InterruptedException {
System.out.println("main start");
Thread t = new Thread(() -> {
System.out.println("worker start");
try { Thread.sleep(1000); } catch (InterruptedException e) {}
System.out.println("worker end");
});
t.start();
System.out.println("main end");
// main 线程结束后,JVM 不退出
// 因为 t 是非守护线程,还在运行
// 只有 t 结束后,JVM 才退出
}
}2. 调用 System.exit()
这是主动退出。可以调用 System.exit(int status) 强制终止 JVM:
public class ForceExit {
public static void main(String[] args) {
try {
// 模拟某些初始化工作
init();
// 启动应用
start();
} finally {
// 无论发生什么,最后清理并退出
cleanup();
System.exit(0);
}
}
}3. 异常或错误导致 JVM 终止
这是非正常退出。比如:
OutOfMemoryError:堆内存耗尽StackOverflowError:栈深度超限- JVM 内部错误(虚拟机错误,
InternalError)
public class AbnormalExit {
public static void main(String[] args) {
// 无限递归 → StackOverflowError → JVM 退出
recursive();
}
static void recursive() {
recursive(); // 没有终止条件
}
}4. 外部强制终止
操作系统层面用 kill -9 直接杀掉进程,或者 IDE 的「停止」按钮,都属于外部强制终止。这种情况下 JVM 的 shutdown hook 不会执行。
Shutdown Hook:JVM 退出前的最后机会
JVM 提供了一种机制,在真正退出之前执行一些清理工作——Shutdown Hook。
public class ShutdownHookDemo {
public static void main(String[] args) {
// 注册一个关闭钩子
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.out.println("Shutdown hook executed!");
// 释放资源、关闭文件、发送告警等
}));
System.out.println("Application is running");
// 模拟业务逻辑
try { Thread.sleep(2000); } catch (InterruptedException e) {}
System.out.println("Application finished normally");
}
}Shutdown Hook 的执行时机:
- 所有正常线程(
User Daemon)都结束后 System.exit()被调用时- 不会执行:外部
kill -9、JVM 崩溃、Runtime.halt()
常用于:释放资源、关闭连接、输出日志、发送告警等。
JVM 退出与守护线程
Java 线程分为守护线程(Daemon Thread) 和用户线程(User Thread)。
- 守护线程:为其他线程提供服务,如 GC 线程。JVM 不等待守护线程退出。
- 用户线程:执行具体业务逻辑。JVM 必须等待所有用户线程退出才退出。
public class DaemonThreadDemo {
public static void main(String[] args) {
Thread daemon = new Thread(() -> {
while (true) {
// 模拟监控任务
monitor();
}
});
// 设置为守护线程
daemon.setDaemon(true);
daemon.start();
// 主线程结束
System.out.println("main thread finished");
// JVM 不会等待 daemon 线程,会直接退出
}
}注意:如果主线程在创建 daemon 线程后立即退出,用户线程数为 0,JVM 会退出。daemon 线程还没来得及打印任何东西,进程就结束了。所以上面的代码可能什么都看不到。
JVM 进程与 Java 进程的关系
一个 Java 程序的进程结构:
操作系统进程
│
├── JVM 进程(native)
│ ├── GC 线程(守护线程)
│ ├── 编译器线程(守护线程)
│ ├── 信号处理线程
│ └── ...
│
├── 主线程(用户线程)
├── Worker-1(用户线程)
├── Worker-2(用户线程)
└── ...JVM 崩溃(native 层出错)会导致整个进程退出,而不是仅仅退出 Java 代码。java.lang.OutOfMemoryError 不会导致 JVM 退出(JVM 还能跑),但 SIGSEGV(段错误)会导致 JVM 崩溃。
本节小结
JVM 的一生:
启动(java 命令) → 初始化(堆、GC、引导类) → 运行(执行 main)
↕
所有非守护线程结束 或 System.exit()
↕
退出前(Shudown Hook)
↕
退出关键点:
- JVM 启动后才开始执行
main(),之前是初始化 - JVM 退出 = 所有非守护线程结束,或
System.exit() - Shutdown Hook 是退出前的最后清理机会
- 守护线程不阻塞 JVM 退出,但用户线程会
到这里,「JVM 基础认知」部分的核心概念就讲完了。接下来,我们要进入 JVM 最核心、最复杂的部分——主流 JVM 虚拟机介绍,了解各路虚拟机的来龙去脉。
