一、Class 文件 与 类加载机制 概述
什么是 Class 文件?
- Java 源码(
.java
)经过 javac 编译器 编译生成的字节码文件(.class
); - 由 JVM 识别执行,包含类的完整结构信息(如字段、方法、常量池、访问权限等);
- 是平台无关的中间代码。
什么是类加载机制?
JVM 的操作对象是 Class 文件,JVM 把 Class 文件中描述类的数据结构加载到内存中,并对数据进行校验、解析和初始化,最终形成可以被 JVM 直接使用的类型,这个过程被称为类加载机制。
其中最重要的三个概念就是:类加载器、类加载过程和类加载器的双亲委派模型。
- 类加载器:负责加载类文件,将类文件加载到内存中,生成 Class 对象。
- 类加载过程:加载、验证、准备、解析和初始化。
- 双亲委派模型:当一个类加载器收到类加载请求时,它首先不会自己去尝试加载这个类,而是把请求委派给父类加载器去完成,依次递归,直到最顶层的类加载器,如果父类加载器无法完成加载请求,子类加载器才会尝试自己去加载。
二、类的生命周期
一个类从被加载到虚拟机内存中开始,到从内存中卸载,整个生命周期需要经过七个阶段:加载 (Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化 (Initialization)、使用(Using)和卸载(Unloading)。
- 加载(Loading):通过类加载器读取
.class
文件,生成Class
对象。 - 连接(Linking):
- 验证(Verify):校验 class 文件格式/安全性;
- 准备(Prepare):为类变量(static)分配内存,设置默认值;
- 解析(Resolve):将常量池中的符号引用转化为直接引用。
- 初始化(Initialization):执行静态变量赋值、静态代码块;
- 使用(Using):正常使用类的阶段;
- 卸载(Unloading):类被回收(通常很少发生)。
三、类的装载过程(详细流程)
类装载过程包括三个阶段:载入、连接(包括验证、准备、解析)、初始化。
1、载入:将类的二进制字节码加载到内存中。
2、连接可以细分为三个小的阶段:
- 验证:检查类文件格式是否符合 JVM 规范
- 准备:为类的静态变量分配内存并设置默认值。
- 解析:将符号引用替换为直接引用。
3、初始化:执行静态代码块和静态变量初始化。
在准备阶段,静态变量已经被赋过默认初始值了,在初始化阶段,静态变量将被赋值为代码期望赋的值。
换句话说,初始化阶段是执行类的构造方法的过程。
载入过程JVM 会做什么?
- 1)通过一个类的全限定名来获取定义此类的二进制字节流。
- 2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
- 3)在内存中生成一个代表这个类的
java.lang.Class
对象,作为方法区这个类的各种数据的访问入口。
四、类加载器有哪些?(ClassLoader)
类加载器(ClassLoader)用于动态加载 Java 类到 Java 虚拟机中。主要有四种类加载器:
1、启动类加载器(Bootstrap ClassLoader)负责加载 JVM 的核心类库,如 rt.jar 和其他核心库位于 JAVA_HOME/jre/lib
目录下的类。
2、扩展类加载器(Extension ClassLoader):由 sun.misc.Launcher$ExtClassLoader
(或其它类似实现)实现。负责加载 JAVA_HOME/jre/lib/ext
目录下,或者由系统属性 java.ext.dirs
指定位置的类库。
3、应用程序类加载器(Application ClassLoader):由 sun.misc.Launcher$AppClassLoader
(或其它类似实现)实现。
负责加载系统类路径(classpath)上的类库,通常是我们在开发 Java 应用程序时的主要类加载器。
我们编写的任何类都是由应用程序类加载器加载的,除非显式使用自定义类加载器。
4、用户自定义类加载器 (User-Defined ClassLoader),我们可以通过继承 java.lang.ClassLoader
类来创建自己的类加载器。
这种类加载器通常用于加载网络上的类、执行热部署(动态加载和替换应用程序的组件)或为了安全目的自定义类的加载方式。
类加载器 | 加载范围 |
---|---|
Bootstrap ClassLoader(启动类加载器) | 加载 JDK 的核心类,如 java.lang.* ,用 C++ 编写,不是 Java 类 |
Extension ClassLoader(扩展类加载器) | 加载 jre/lib/ext 或指定目录下的扩展类 |
App ClassLoader(应用程序类加载器) | 加载 classpath 下的应用类 |
自定义 ClassLoader(用户自定义类加载器) | 程序员可自定义加载逻辑,通常用于插件机制、类隔离等 |
五、什么是双亲委派机制?
定义:
- 类加载器在加载类时,先将请求委托给父加载器,父加载器无法加载时,子加载器才尝试加载。
- 从上到下依次委托:App → Ext → Bootstrap
- 当一个类加载器需要加载某个类时,它首先会请求其父类加载器加载这个类。
- 这个过程会一直向上递归,也就是说,从子加载器到父加载器,再到更上层的加载器,一直到最顶层的启动类加载器。
- 启动类加载器会尝试加载这个类。如果它能够加载这个类,就直接返回;如果它不能加载这个类(因为这个类不在它的搜索范围内),就会将加载任务返回给委托它的子加载器。
- 子加载器接着尝试加载这个类。如果子加载器也无法加载这个类,它就会继续向下传递这个加载任务,依此类推。
- 这个过程会继续,直到某个加载器能够加载这个类,或者所有加载器都无法加载这个类,最终抛出 ClassNotFoundException。
好处:
- 避免重复加载:父加载器加载的类,子加载器无需重复加载。
- 核心类安全性:确保核心类不会被篡改(比如
java.lang.String
只能由 Bootstrap ClassLoader 加载,防止被篡改。)
示例流程:
加载 java.lang.String
→ AppClassLoader → ExtClassLoader → Bootstrap → 成功加载
六、如何破坏双亲委派机制?
- 自定义 ClassLoader,重写
loadClass()
方法,不调用super.loadClass()
; - 使用第三方框架,如 Tomcat、JSP 容器、Spring Boot 的隔离加载;
- 常用于:热部署、插件隔离、多个版本并存 等场景。
总结:
问题 | 回答 |
---|---|
类加载器有哪些? | Bootstrap、Ext、App、自定义 |
类的生命周期? | 加载 → 连接(验证/准备/解析)→ 初始化 → 使用 → 卸载 |
什么是双亲委派? | 类加载器先委托父类加载,父类无法加载才自己尝试加载 |
为什么要用双亲委派? | 避免重复加载,保护核心类安全 |
如何打破双亲委派? | 自定义类加载器,重写 loadClass() 方法 |