

Java类的生命周期包括以下阶段:
加载(Loading):当Java程序需要使用某个类时,JVM会检查该类是否已经被加载,如果没有加载,JVM会从磁盘中读取该类的字节码文件并创建一个Class对象,然后将该Class对象存放在方法区中。
验证(Verification):在加载类的过程中,JVM会对该类的字节码进行验证,以确保它符合Java虚拟机规范,不会对虚拟机造成安全上的威胁。
准备(Preparation):在准备阶段,JVM会为类的静态变量分配内存并设置默认值(0或null),并将这些变量存放在方法区中。
解析(Resolution):在解析阶段,JVM会将类中的符号引用转换为直接引用,以便于JVM能够快速访问类中的方法和变量。
初始化(Initialization):在初始化阶段,JVM会执行类的初始化代码,包括静态变量赋值和静态代码块的执行。如果该类有父类,JVM会先初始化父类。
使用(Using):在使用阶段,JVM会调用类中的方法和访问类中的变量。
卸载(Unloading):当JVM确定某个类实例已经不再被使用时,会将该类的Class对象从方法区中移除,这个过程称为卸载。





java -cp sa-jdi.jar sun.jvm.hotspot.HSDB

验证(Verification):在加载类的过程中,JVM会对该类的字节码进行验证,以确保它符合Java虚拟机规范,不会对虚拟机造成安全上的威胁。
准备(Preparation):在准备阶段,JVM会为类的静态变量分配内存并设置默认值(0或null),并将这些变量存放在方法区中。
解析(Resolution):在解析阶段,JVM会将类中的符号引用转换为直接引用,以便于JVM能够快速访问类中的方法和变量。
连接(Linking)阶段的第一个环节是验证,验证的主要目的是检测Java字节码文件是否遵守了《Java虚拟机规
范》中的约束。这个阶段一般不需要程序员参与。
主要包含如下四部分,具体详见《Java虚拟机规范》:









初始化阶段会执行静态代码块中的代码,并为静态变量赋值
初始化阶段会执行字节码文件中clinit部分的字节码指令



clinit方法中的执行顺序与Java中编写的顺序是一致的

以下几种方式会导致类的初始化
public class Demo1 {
public static void main(String[] args) {
int i = Demo2.i;
System.out.println(i);
}
}
class Demo2{
static {
System.out.println("初始化了...");
}
public static final int i = 0;
}
public class Demo3 {
public static void main(String[] args) throws ClassNotFoundException {
Class<?> clazz = Class.forName("init.ways.Demo4");
}
}
class Demo4{
static {
System.out.println("初始化了...");
}
}
public class Demo5 {
static {
System.out.println("Demo5初始化了...");
}
public static void main(String[] args) throws ClassNotFoundException {
new Demo6();
}
}
class Demo6{
static {
System.out.println("Demo6初始化了...");
}
}
添加-XX:+TraceClassLoading 参数可以打印出加载并初始化的类
public class Test1 {
public static void main(String[] args) {
System.out.println("A");
new Test1();
new Test1();
}
public Test1(){
System.out.println("B");
}
{
System.out.println("C");
}
static {
System.out.println("D");
}
}
代码的执行结果为:
D
A
C
B
C
B
原因是:
1. 静态代码块 `static {...}` 会在类加载时执行,因此会先输出 "D"。
2. `main` 方法中先输出 "A"。
3. 接着创建了两个 `Test1` 对象,因此会分别调用两次构造方法 `public Test1() {...}`。
4. 在构造方法之前,非静态代码块 `{...}` 会先执行,因此会先输出 "C"。
5. 每次创建 `Test1` 对象时,都会执行一次非静态代码块和构造方法,因此会输出两次 "C" 和两次 "B"。


clinit指令在特定情况下不会出现,比如:如下几种情况是不会进行初始化指令执行的

直接访问父类的静态变量,不会触发子类的初始化
子类的初始化clinit调用之前,会先调用父类的clinit初始化方法

如果把new B02()去掉,结果如下

public class Test2 {
public static void main(String[] args) {
Test2_A[] arr = new Test2_A[10];
}
}
class Test2_A {
static {
System.out.println("Test2 A的静态代码块运行");
}
}
- 静态代码块是在类加载时进行初始化的,具体来说,当 JVM 加载一个类时,会先加载该类的父类(如果有父类的话),然后再加载该类本身。在加载类的过程中,JVM 会执行该类的静态代码块,以完成静态成员变量的初始化和其他一些静态操作。
public class Test4 {
public static void main(String[] args) {
System.out.println(Test4_A.a);
}
}
class Test4_A {
public static final int a = Integer.valueOf(1);
static {
System.out.println("Test3 A的静态代码块运行");
}
}
Test3 A的静态代码块运行
1
public class Test4 {
public static void main(String[] args) {
System.out.println(Test4_A.a);
}
}
class Test4_A {
public static final int a =1;
static {
System.out.println("Test3 A的静态代码块运行");
}
}
1
如果一个类中定义了静态成员变量,并且这些成员变量都是编译期常量(比如使用 final 关键字修饰的常量),那么在访问这些常量时,编译器会直接将常量的值嵌入到字节码中,而不是在运行时动态计算。这个过程被称为编译期常量折叠(Compile-Time Constant Folding)。
在这种情况下,如果静态代码块中的代码并没有涉及到这些常量,那么在访问这些常量时,并不会触发类的初始化,也就不会执行静态代码块中的代码。因此,如果一个类中定义了静态成员变量,并且这些成员变量都是编译期常量,并且静态代码块中的代码并没有涉及到这些常量,那么在访问这些常量时,不会触发静态代码块的执行。
创建对象:在使用阶段中,可以通过关键字new创建一个类的对象
String str = new String("Hello World!");
实例化:在使用阶段中,可以通过构造方法来实例化一个类的对象。
Date date = new Date();
调用方法:在使用阶段中,可以通过对象来调用类中的方法
int length = str.length();
访问变量:在使用阶段中,可以通过对象来访问类中的变量
double pi = Math.PI;
卸载阶段是Java类生命周期中的最后一个阶段。在卸载阶段中,Java虚拟机会卸载不再需要的类和类加载器,从而释放内存空间。
主要作用是清理内存,提高程序性能。
一个类被卸载的条件是它的所有实例都已经被销毁,同时该类的类对象和类加载器也都已经被销毁。
Java虚拟机在卸载类时,会调用该类的finalize()方法,该方法可以被子类重写以完成一些清理工作。在finalize()方法中,可以关闭文件、释放资源等操作。如果一个类没有重写finalize()方法,Java虚拟机会自动调用默认的finalize()方法。
注意,Java虚拟机并不保证在任何时刻都会卸载一个类。在某些情况下,Java虚拟机可能会选择不卸载一个类,而是将其保留在内存中以提高程序性能。例如,如果一个类被频繁使用,Java虚拟机可能会将其保留在内存中以避免重复加载。

