JVM 的生命周期是指 JVM 实例从启动到终止的整个过程。一个 Java 应用程序通常对应一个 JVM 实例(除非使用 JNI 等技术在同一进程中创建多个 JVM 实例,但这并不常见)。
JVM 的生命周期阶段:
启动 (Startup):
- 创建 JVM 实例: 当你使用
java
命令运行 Java 程序时,操作系统会创建一个新的进程,并在该进程中启动一个 JVM 实例。 - 加载和初始化:
- 查找并加载 JDK: 找到 JRE (Java Runtime Environment) 的位置.
- 创建引导类加载器 (Bootstrap Class Loader): 加载核心类库(
rt.jar
等)。 - 创建扩展类加载器 (Extension Class Loader) 和应用程序类加载器 (Application Class Loader): 分别加载扩展类库和应用程序的类。
- 加载
main
方法所在的类: 使用应用程序类加载器加载包含main
方法的类。 - 初始化类: 执行类的静态初始化块和静态变量赋值。
- 命令行参数: 解析传入
java
命令的参数.
- 创建 JVM 实例: 当你使用
运行 (Execution):
- 执行
main
方法: JVM 调用main
方法,开始执行 Java 程序。 - 类加载: 在程序运行过程中,根据需要动态加载类。
- 字节码执行: JVM 的执行引擎解释或编译执行字节码。
- 内存管理: JVM 分配和管理内存,进行垃圾回收。
- 线程管理: JVM 创建和管理线程。
- 异常处理: JVM 处理程序中发生的异常。
- 执行
终止 (Termination):
- 正常终止:
- 当程序执行完
main
方法,并且所有非守护线程(non-daemon threads)都已结束时,JVM 正常终止。 - 可以通过调用
System.exit(status)
方法显式终止 JVM。status
是退出状态码(0 表示正常退出,非 0 表示异常退出)。
- 当程序执行完
- 异常终止:
- 如果程序中发生了未捕获的异常,并且没有设置默认的未捕获异常处理器,JVM 会异常终止。
- 可以通过
Thread.setDefaultUncaughtExceptionHandler()
设置默认的未捕获异常处理器。
- 外部终止:
- 用户可以通过操作系统命令(例如,在 Linux 中使用
kill
命令)强制终止 JVM 进程。 - JVM 可能会收到操作系统发送的终止信号(例如,SIGTERM)。
- 用户可以通过操作系统命令(例如,在 Linux 中使用
- 钩子 (Shutdown Hooks):
- 在 JVM 终止之前,可以注册一些钩子函数(shutdown hooks),用于执行一些清理操作(例如,关闭数据库连接、释放资源等)。
- 可以使用
Runtime.getRuntime().addShutdownHook(Thread hook)
方法注册钩子函数。 - 钩子函数会在以下情况下执行:
- 程序正常退出。
- 调用
System.exit()
。 - 用户中断程序(例如,按下 Ctrl+C)。
- 系统关闭。
- 钩子函数执行的顺序是不确定的。
- 正常终止:
总结流程图:
+----------------+
| 启动 JVM | (java 命令)
+----------------+
|
V
+----------------+
| 加载和初始化 |
| - 查找并加载 JDK |
| - 创建类加载器 |
| - 加载 main 类 |
| - 初始化类 |
+----------------+
|
V
+----------------+
| 运行程序 |
| - 执行 main 方法|
| - 类加载 |
| - 字节码执行 |
| - 内存管理 |
| - 线程管理 |
| - 异常处理 |
+----------------+
|
V
+----------------+
| 终止 JVM |
| - 正常终止 |
| - 异常终止 |
| - 外部终止 |
| - 执行钩子函数 |
+----------------+
代码示例 (演示钩子函数):
public class JVMLifecycle {
public static void main(String[] args) {
// 注册钩子函数
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.out.println("JVM 即将终止,执行清理操作...");
// 在这里执行清理操作 (例如,关闭数据库连接、释放资源等)
}));
System.out.println("程序开始执行...");
// 模拟程序运行一段时间
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("程序执行完毕...");
// 可以选择显式退出 (也可以不调用 exit,让程序自然结束)
// System.exit(0);
}
}
运行结果:
程序开始执行...
程序执行完毕...
JVM 即将终止,执行清理操作...
注意:
- 如果程序中存在死循环或无限等待,JVM 可能永远不会终止。
- 守护线程 (daemon thread) 不会阻止 JVM 终止。当所有非守护线程都结束后,JVM 会强制终止所有守护线程。
- 钩子函数应该尽量简短,避免执行耗时的操作,否则可能会导致 JVM 无法正常终止。
- 不要在钩子函数中调用
System.exit()
,否则会导致死循环。