类加载机制的深度解析
1. 类加载运行全过程
当我们用java命令运行某个类的main函数启动程序时,首先需要通过类加载器把主类加载到 JVM。
有如下几步: 加载 >> 验证 >> 准备 >> 解析 >> 初始化 >> 使用 >> 卸载
加载:
- 通过类的全限定名获取其二进制字节流:可以从本地文件系统、网络或其他来源获取字节码。
- 将字节流转换为方法区中的对应的类结构。
加载的方式
- 启动类加载器(Bootstrap ClassLoader):负责加载核心类库(如
rt.jar
中的类)。 - 扩展类加载器(Extension ClassLoader):负责加载扩展类库(如
jre/lib/ext
目录下的类)。 - 应用程序类加载器(Application ClassLoader):负责加载用户类路径(classpath)中的类。
- 自定义类加载器:可以通过继承
java.lang.ClassLoader
实现自定义加载逻辑。
验证:
- 校验字节码文件的正确性且符合 JVM 规范,防止恶意代码破坏虚拟机的安全性。
- 验证是一个耗时但必要的过程,确保了类的正确性和安全性。
准备:
- 给类的静态变量分配内存,
- 设置默认值:将静态变量初始化为其类型的零值(如
int
类型为0
,boolean
类型为false
,引用类型为null
) 。(如int
类型为0
,boolean
类型为false
,引用类型为null
)
1
2
3public class Example {
static int value = 123; // 静态变量
}- 在准备阶段,
value
被分配内存并初始化为0
。 - 初始化阶段才会将
value
设置为123
。
解析:将符号引用替换为地址引用
1
2- 符号引用是以文本形式表示的目标地址或对象的逻辑名称。它并不直接指向内存中的实际位置,而是通过某种间接的方式(如名称、路径等)描述目标对象的位置
- 直接引用是指向目标对象在内存中的实际地址。它是具体的物理地址,可以直接用于访问目标对象。1
2
3举例:
假设有一个类 `A` 中定义了一个方法 `void foo()`,在字节码文件中,这个方法可能以符号引用的形式表示为:Method #5; // NameAndType foo:()V, 这里的 `#5` 是一个索引,指向常量池中的某个条目,而 `NameAndType foo:()V` 表示方法的名字和签名。
在运行时,JVM 将上述符号引用解析为实际的内存地址,例如:0x7f8000001234, 这个地址可以直接定位到方法 `foo()` 的代码段。初始化:
对类的静态变量初始化为指定的值
执行静态代码块
2. 类加载器
- **引导类加载器 (Bootstrap ClassLoader)**:
- 职责:加载 Java 核心库中的类,如
java.lang.*
、java.util.*
等。 - 加载路径:JVM 启动时指定的
rt.jar
或者jmod
文件中的类。 - 特点:由 JVM 实现,使用本地代码,不是一个普通的 Java 类。
- 职责:加载 Java 核心库中的类,如
- **扩展类加载器 (Extension ClassLoader)**:
- 职责:加载标准扩展库中的类,位于
JAVA_HOME/lib/ext
目录或由系统属性java.ext.dirs
指定的目录。 - 加载路径:
lib/ext
目录中的 JAR 文件。 - 特点:由 Java 实现,继承自
ClassLoader
类。
- 职责:加载标准扩展库中的类,位于
- **系统类加载器 (System ClassLoader) / 应用程序类加载器 (Application ClassLoader)**:
- 职责:加载用户类路径 (
classpath
) 中的类,包含用户定义的类和第三方库。 - 加载路径:通过命令行参数
-classpath
或环境变量CLASSPATH
指定的路径。 - 特点:默认的类加载器,可以通过
ClassLoader.getSystemClassLoader()
获取。
- 职责:加载用户类路径 (
- **自定义类加载器 (Custom ClassLoader)**:
- 职责:由用户定义,可以从任意位置加载类,例如网络、数据库等。
- 加载路径:用户定义。
- 特点:用户可以通过继承
ClassLoader
类并覆盖findClass
方法来自定义类加载逻辑。
3. 双亲委派机制
当某个类加载器需要加载某个.class
文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类。

loadClass 方法的主要职责就是实现双亲委派机制:首先检查这个类是不是已经被加载过了,如果加载过了直接返回,否则委派给父加载器加载,这是一个递归调用,一层一层向上委派,最顶层的类加载器(启动类加载器)无法加载该类时,再一层一层向下委派给子类加载器加载。
1 |
|
3.1 作用
防止加载同一个.class。通过委托去询问上级是否已经加载过该.class,如果加载过了,则不需要重新加载。保证了数据安全。
保证核心.class不被篡改。通过委托的方式,保证核心.class不被篡改,即使被篡改也不会被加载,即使被加载也不会是同一个class对象,因为不同的加载器加载同一个.class也不是同一个Class对象。这样则保证了Class的执行安全。
3.2 如何打破双亲委派?
打破双亲委派,其实就是不走双亲委派那一套,而是走自定义的类加载器。
双亲委派的机制是ClassLoader中的loadClass方法实现的,打破双亲委派,其实就是重写这个方法,来用我们自己的方式来实现即可。典型的打破双亲委派模型的框架和中间件有tomcat。
1 |
|
类加载机制的深度解析
http://example.com/类加载机制的深度解析/