JVM类与类加载
java的类字节码文件如何加载到内存中的?
类加载过程
类加载的触发条件
JVM并不会一次性加载所有类,而是采用“按需加载”策略,即在需要的时候才会进行类的加载。
触发类加载的操作
一般在这些情况下,如果类没有被加载,那么会被自动加载:
- 使用
new
关键字创建对象时
MyClass obj = new MyClass(); |
- 访问类的静态变量(包括读取或写入,不是
final
修饰的常量)时
int value = MyClass.staticField; |
- 调用类的静态方法时
MyClass.staticMethod(); |
- 使用反射机制(如
Class.forName()
、Class.getMethod()
等)时
Class<?> clazz = Class.forName("com.example.MyClass"); |
- 子类初始化时,如果父类尚未初始化,会先触发父类的加载
class A { static int a = 10; } |
- 作为
main
方法所在的类
public class Main { |
不会触发类加载的操作
- 访问
final
修饰的静态变量,final
变量在编译时已确定,不会触发类的初始化
class A { |
- 通过数组定义类的引用,只是创建了一个引用类型数组,并未真正使用A类
A[] array = new A[10]; // A 类不会被加载 |
- 通过子类访问父类的静态变量
class A { |
类加载过程
Java 类的加载过程主要包括加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)五个阶段
加载
- 通过类的全限定名查找字节码文件(如
.class
文件) - 将字节码文件转换为内存中的二进制数据
- 在方法区(JDK 8之前)或元空间(JDK 8及之后)中创建类的运行时数据结构
- 在堆内存中生成一个
Class
对象,作为方法区或元空间中数据的访问入口
验证
- 检查字节码文件的正确性和安全性,确保其符合JVM规范
- 包括文件格式验证、元数据验证、字节码验证和符号引用验证
- 文件格式验证:是否符合
.class
文件规范(开头八位数,即魔数CAFEBABE
) - 元数据验证:类、方法、字段的定义是否合法
- 字节码检查:方法调用是否合法,是否越界访问
- 符号引用检查:解析阶段前,确保引用的类、方法、字段存在
- 文件格式验证:是否符合
- 如果验证失败,会抛出
VerifyError
准备
- 为类的静态变量分配内存,并赋默认值(非初始化值)
- 变量赋的是默认值(
int
->0
,boolean
->false
,reference
->null
)。 - 如果静态变量是常量(
final
),则直接赋值为指定的初始值
解析
将常量池中的符号引用转换为直接引用
符号引用:类的字段、方法的名称和描述符
com/example/A
之类的字符串引用类A.f
代表字段A.m()
代表方法
直接引用:字段、方法在内存中的实际地址
初始化
执行类的初始化代码,包括静态变量的赋值和静态代码块的执行
按照定义顺序执行类的静态初始化代码,包括:
- 静态变量赋值
- 静态代码块
- 执行
<clinit>()
方法
执行顺序:
- 父类静态变量 & 静态代码块
- 子类静态变量 & 静态代码块
- 父类实例变量 & 构造代码块
- 父类构造方法
- 子类实例变量 & 构造代码块
- 子类构造方法
类的卸载
条件:
- 类的
ClassLoader
被回收 - 该类的所有实例都不可达(GC)
- 没有其他类引用该类
特点:
- 只会卸载用户自定义类,JDK的核心类不会被卸载
类加载器
Java提供了类加载器,以便更好地控制类加载,可以自定义类加载器,也可以使用官方自带的类加载器去加载类。对于任意一个类,都必须由加载它的类加载器和这个类本身一起共同确立其在Java虚拟机中的唯一性。也就是说,一个类可以由不同的类加载器加载,并且,不同的类加载器加载的出来的类,即使来自同一个Class文件,也是不同的,只有两个类来自同一个Class文件并且是由同一个类加载器加载的,才能判断为是同一个。
默认情况下,所有的类都是由JDK自带的类加载器进行加载,类加载器实现了类的动态加载、隔离和安全性控制。
概述
类加载器是JVM的一部分,用于查找、加载和定义Java类。主要负责:
- 加载类文件(从磁盘、网络、JAR包等)
- 将字节码转换为 Class 对象
- 缓存已加载的类,避免重复加载
Java采用双亲委派机制来组织多个类加载器,确保类的加载顺序和安全性
主要有三种由JDK提供的类加载器:
类加载器 | 作用 | 负责加载的类 |
---|---|---|
Bootstrap ClassLoader(启动类加载器) | 最顶层的类加载器,由 JVM 实现 | rt.jar (如 java.lang.String 、java.util.List ) |
Extension ClassLoader(扩展类加载器) | 加载扩展库中的类 | lib/ext 目录下的 JAR |
Application ClassLoader(应用类加载器) | 加载应用程序的类 | classpath 指定的类 |
工作机制
- 检查缓存:如果类已加载,则直接返回Class对象(避免重复加载)
- 双亲委派
- 先让父类加载器尝试加载类
- 如果找不到,再由当前类加载器加载
- 转换&解析
- 将字节码转换成Class对象,并存入JVM方法区
- 返回Class对象,允许实例化对象
双亲委派
- 当一个类加载器要加载某个类时,先让自己的“父加载器”尝试加载
- 如果父加载器找不到该类,才由当前类加载器自己加载
工作流程:
- 类加载请求从最底层的
ClassLoader
向上层传递。 - 顶层加载器(Bootstrap ClassLoader)先尝试加载
- 如果上层加载器找不到类,才交给当前类加载器进行加载
- 如果类已加载,直接返回
Class
对象
自定义类加载器
Java允许自定义类加载器,用于加载网络、加密、动态生成的类,通过继承 java.lang.ClassLoader
类,可以实现自定义的类加载器。举例:
import java.io.ByteArrayOutputStream; |
findClass
方法:
- 自定义类加载器需要重写
findClass
方法,用于加载类的字节码文件
defineClass
方法:
- 将字节码文件转换为
Class
对象
loadClassData
方法:
- 从指定路径读取字节码文件