生命周期

加载(Loading)

  • JVM查找并加载类的二进制数据
  • 创建Class对象
  • 不执行任何代码

链接(Linking)

  • 验证:确保类文件格式正确
  • 准备:为静态变量分配内存并设置默认值
  • 解析:将符号引用转换为直接引用

初始化(Initialization)

触发时机(首次以下情况发生时):

  • 创建类的实例 (new)
  • 调用类的静态方法
  • 访问类的静态字段(非常量)
  • 使用反射Class.forName()
  • 初始化子类时父类未初始化

初始化顺序:

  1. 静态字段赋值
  2. 静态初始化块

类加载时机

简单来说就是要使用到类的各种方法,字段时,反射获取类时就会加载类

具体来说比如:实例化一个类时,访问类的静态方法、静态字段,构造方法,员方法,成员变量以及反射forname获取类时会触发类加载机制

接下来通过代码感受一下类加载。先写一个java bean加上一些调试输出

  • User.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package org.javasec.FirstReflect;

public class User {
static {
System.out.println("[User]: 静态初始化块-执行成功");
}

{
System.out.println("[User]: 实例初始化-执行成功");
}
private String usernane;
private int age;
static String flag="[User]: 静态字段-访问成功";

static void staticMethod() {
System.out.println("[User]: 静态方法-执行成功");
}

public User() {
System.out.println("[User]: 无参构造方法-执行成功");
}
public User(String username,int age) {
this.usernane = username;
this.age = age;
}
public String getUsernane() {
System.out.println("[User]: getter方法-执行成功");
return usernane;
}
public void setUsernane(String usernane) {
this.usernane = usernane;
}
public int getAge() {
return age;
}
public void setage(int age) {
this.age = age;
}
}
  • TestClassLoad
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package org.javasec.FirstReflect;
import org.javasec.FirstReflect.User;

public class TestClassLoad {
static void test() {
System.out.println("===调用静态方法-开始===");
User.staticMethod();
System.out.println("===调用静态方法-结束===\n");

System.out.println("===访问静态成员变量-开始===");
String f1= User.flag;
System.out.println(f1);
System.out.println("===访问静态成员变量-结束===\n");

System.out.println("===实例化对象-开始===");
User user1 = new User();
System.out.println("===实例化对象-结束===\n");

System.out.println("===调用有参构造方法-开始===");
User user2 = new User("admin",20);
System.out.println("===调用有参构造方法-结束===\n");

System.out.println("===调用getter方法-开始===");
System.out.println(user2.getUsernane());
System.out.println("===调用getter方法-结束===\n");

}
}
  • Main.java
1
2
3
4
5
6
7
8
package org.javasec.FirstReflect;
import org.javasec.FirstReflect.TestClassLoad;

public class Main {
public static void main(String[] args) throws Exception {
TestClassLoad.test();
}
}

输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
===调用静态方法-开始===
[User]: 静态初始化块-执行成功
[User]: 静态方法-执行成功
===调用静态方法-结束===

===访问静态成员变量-开始===
[User]: 静态字段-访问成功
===访问静态成员变量-结束===

===实例化对象-开始===
[User]: 实例初始化-执行成功
[User]: 无参构造方法-执行成功
===实例化对象-结束===

===调用有参构造方法-开始===
[User]: 实例初始化-执行成功
[User]: 有参构造方法-执行成功
===调用有参构造方法-结束===

===调用getter方法-开始===
[User]: getter方法-执行成功
admin
===调用getter方法-结束===

我们先仅仅保留实例化的操作,其他操作注释掉

image-20251104151554330

输出

1
2
3
4
5
===实例化对象-开始===
[User]: 静态初始化块-执行成功
[User]: 实例初始化-执行成功
[User]: 无参构造方法-执行成功
===实例化对象-结束===

发现仍然仍然触发了静态初始化块和实例初始化这一类加载生命周期的初始化阶段。

是的,这是一个重要的细节,就是在类加载时,更准确的是首次加载时,会触发初始化操作:1.直接进行静态字段赋值,2.直接执行静态初始化代码块!如果后续再次加载已经加载过的类,就不会再次触发这些操作了

或者更细分地来说的话类加载这个步骤仅仅指加载那一步,整个生命周期包括了,类加载,链接,初始化(类初始化,对象实例化代码块执行等等操作)等等步骤,但是我们习惯上把初始化也包括在广义的类加载中来谈论

1
2
3
4
5
6
7
8
// 触发:
// 1. 类加载(Loading)- JVM加载字节码
// 2. 类初始化(Initialization):
// - static String flag = "[User]: 静态字段-访问成功" // 静态字段赋值
// - static { System.out.println("[User]: 静态初始化块-执行成功"); } // 静态块执行
// 3. 对象实例化:
// - { System.out.println("[User]: 实例初始化-执行成功"); } // 实例块
// - public User() { ... } // 构造函数