加载过程 & 双亲委托机制
MoMo Lv5

类的加载过程

类的加载过程由类加载器(ClassLoader)管理,主要分为以下几个阶段:

1. 加载(Loading)

  • 步骤:类加载器读取类的二进制数据(通常从.class 文件中),并将其转换为方法区中的运行时数据结构。
  • 结果:生成一个 java.lang.Class 对象,表示该类在内存中的状态。

2. 验证(Verification)

  • 步骤:确保类的字节码符合 Java 语言规范和 JVM 规范,防止恶意代码对 JVM 的破坏。
  • 结果:验证通过,进入下一个阶段;否则,抛出验证错误。

3. 准备(Preparation)

  • 步骤:为类的静态变量分配内存,并将其初始化为默认值(零值)。
  • 结果:静态变量已分配内存,但未初始化为指定的值。

4. 解析(Resolution)

  • 步骤:将常量池中的符号引用转换为直接引用,包括类、接口、字段和方法的解析。
  • 结果:解析成功,符号引用转换为直接引用。

5. 初始化(Initialization)

  • 步骤:执行类的静态初始化块和静态变量的初始化赋值。
  • 结果:类的静态变量被初始化为指定值,静态初始化块被执行。

6. 使用(Using)

  • 步骤:类已被加载、验证、准备、解析和初始化,可以被使用。
  • 结果:类的实例可以被创建,类的方法可以被调用。

7. 卸载(Unloading)

  • 步骤:类不再被使用,类加载器卸载类并释放其占用的内存。
  • 结果:类的内存被回收,类对象被卸载。

对象的加载过程

对象的加载过程涉及类的实例化,主要步骤如下:

1. 分配内存

  • 步骤:在堆中分配内存空间。
  • 结果:分配空间的大小由类的定义决定。

2. 初始化默认值

  • 步骤:将分配的内存空间初始化为默认值(零值)。
  • 结果:对象的成员变量被初始化为默认值。

3. 设置对象头

  • 步骤:设置对象头,包含对象的元数据信息,如哈希码、GC 信息、类型指针等。
  • 结果:对象头被正确设置。

4. 执行构造方法

  • 步骤:执行类的构造方法,初始化对象的成员变量。
  • 结果:对象的成员变量被初始化为指定值,构造方法完成执行。

委派机制

双亲委托机制是指当一个类加载器收到一个类加载请求时,该类加载器首先会把请求委派给父类加载器。每个类加载器都是如此,只有在父类加载器在自己的搜索范围内找不到指定类时,子类加载器才会尝试自己去加载。
双亲委派机制是 Java 类加载器的一种工作模式,用于避免类的重复加载和确保类的安全性。具体机制如下:

  1. 机制描述
    • 当一个类加载器需要加载一个类时,它首先将请求委派给它的父类加载器。
    • 父类加载器再将请求委派给它的父类加载器,依此类推,直到顶层的 Bootstrap ClassLoader。
    • 如果父类加载器可以完成类加载任务,就返回成功;否则,子类加载器才会尝试加载该类。
  2. 优点
    • 避免重复加载:确保核心类库不会被重复加载,避免类的多次定义。
    • 安全性:防止核心类库被恶意篡改。
      • Java虚拟机只会在不同的类的类名相同且加载该类的加载器均相同的情况下才会判定这是一个类。如果没有双亲委派机制,同一个类可能就会被多个类加载器加载,如此类就可能会被识别为两个不同的类,相互赋值时问题就会出现。

双亲委派机制能够保证多加载器加载某个类时,最终都是由一个加载器加载,确保最终加载结果相同。

没有双亲委派模型,让所有类加载器自行加载的话,假如用户自己编写了一个称为java.lang.Object的类,并放在程序的ClassPath中,系统就会出现多个不同的Object类, Java类型体系中基础行为就无法保证,应用程序就会变得一片混乱。

  1. 类加载器层次
    • Bootstrap ClassLoader:负责加载 JRE 核心类库(<JAVA_HOME>/lib)。
    • Extension ClassLoader:负责加载扩展类库(<JAVA_HOME>/lib/ext)。
    • Application ClassLoader:负责加载应用程序的类路径(classpath)上的类。
    • 自定义 ClassLoader:开发者可以创建自定义类加载器,用于加载特定的类。
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
40
41
42
public abstract class ClassLoader{
//父类加载器
ClassLoader parent;

protected ClassLoader(ClassLoader parentLoader) {
this(parentLoader, false);
}

ClassLoader(ClassLoader parentLoader, boolean nullAllowed) {
if (parentLoader == null && !nullAllowed) {
throw new NullPointerException("parentLoader == null && !nullAllowed");
}
parent = parentLoader;
}

protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
//先找缓存
Class<?> clazz = findLoadedClass(className);
if (clazz == null) {
ClassNotFoundException suppressed = null;
if(parent !=null){
try {
//交给父类加载器加载
clazz = parent.loadClass(className, false);
} catch (ClassNotFoundException e) {
suppressed = e;
}
}
if (clazz == null) {
try {
//父类加载器加载不到,自己加载
clazz = findClass(className);
} catch (ClassNotFoundException e) {
e.addSuppressed(suppressed);
throw e;
}
}
}

return clazz;
}
}

总结

  • 类的加载过程:包括加载、验证、准备、解析、初始化、使用和卸载阶段。
  • 对象的加载过程:涉及内存分配、初始化默认值、设置对象头和执行构造方法。
  • 双亲委派机制:通过委派机制确保类加载的安全性和一致性,防止类的重复加载。
Powered by Hexo & Theme Keep
Unique Visitor Page View