对于运行在VPS或公有云虚拟机上的Java应用来说,理解JVM(Java虚拟机)如何管理类是优化性能和排查启动问题的关键。Java类的生命周期涉及五个核心阶段:加载、连接、初始化、使用和卸载。
本文将重点关注其中最关键且最易操作观察的阶段:加载(Loading)和初始化(Initialization),并通过一个实例来演示它们是如何触发执行的。
Java类生命周期的五个阶段
- 加载 (Loading):查找并导入类的二进制数据(.class文件)。
- 连接 (Linking):
- 验证 (Verification):确保.class文件符合JVM规范。
- 准备 (Preparation):为类的静态变量分配内存,并设置默认初始值(例如,int设置为0,引用设置为null)。
- 解析 (Resolution):将符号引用转换为直接引用。
- 初始化 (Initialization):执行类构造器
() 方法。这个方法是编译器自动收集类中所有静态变量的赋值动作和静态代码块(static {})中的语句而生成的。这是我们能直接控制代码执行顺序的关键点。 - 使用 (Using):对象实例化和方法调用。
- 卸载 (Unloading):当类不再被使用时,从内存中移除。
重点实操:观察类的初始化顺序
初始化是类生命周期中唯一能主动被程序代码控制的阶段。一个类的主动初始化(即执行
- 遇到new、getstatic、putstatic、或invokestatic指令时(如实例化对象、访问或设置静态变量、调用静态方法)。
- 使用反射(java.lang.reflect)对类进行调用时。
- 初始化一个类的子类时,父类会先被初始化。
- 包含main()方法的那个启动类。
下面的Java代码示例将演示当一个子类被主动初始化时,父类、子类以及主类的静态初始化块的执行顺序。
示例代码:ClassLifecycleDemo.java
我们将创建父类、子类和主类,并在其中设置静态块。
// ClassLifecycleDemo.java
class Parent {
public static String P_STATIC_FIELD = "Parent Static Field Initialized";
static {
System.out.println("--- 1. Parent类:执行静态初始化块(父类先于子类) ---");
}
}
class Child extends Parent {
public static String C_STATIC_FIELD = "Child Static Field Initialized";
static {
System.out.println("--- 2. Child类:执行静态初始化块 ---");
}
}
public class ClassLifecycleDemo {
static {
System.out.println("--- 3. 主类:ClassLifecycleDemo 静态初始化块执行(启动时最先加载) ---");
}
public static void main(String[] args) {
System.out.println("--- 4. main方法开始执行 ---");
// 访问子类的静态字段,这将触发子类(Child)的主动初始化,
// 进而确保其父类(Parent)也完成初始化。
System.out.println("访问子类静态字段结果:" + Child.C_STATIC_FIELD);
}
}
VPS/VM上运行步骤
在你的VPS或虚拟机命令行中,执行以下步骤来观察初始化顺序:
# 1. 编译Java文件
javac ClassLifecycleDemo.java
# 2. 运行程序
java ClassLifecycleDemo
预期输出结果
--- 3. 主类:ClassLifecycleDemo 静态初始化块执行(启动时最先加载) ---
--- 4. main方法开始执行 ---
--- 1. Parent类:执行静态初始化块(父类先于子类) ---
--- 2. Child类:执行静态初始化块 ---
访问子类静态字段结果:Child Static Field Initialized
结论:
- 主类的静态块最先执行,因为它是程序入口。
- 当在main方法中访问Child.C_STATIC_FIELD时,JVM触发了Child类的主动初始化。
- 根据JVM规范,在初始化一个子类之前,必须先保证其父类(Parent)已经初始化完成,因此Parent的静态块在Child的静态块之前执行。
汤不热吧