Java类加载机制详解
Java类加载机制详解
类加载过程
JVM加载一个类需要经过五个阶段:加载 → 验证 → 准备 → 解析 → 初始化。
源代码(.java) → 编译(.class) → 类加载 → 验证 → 准备 → 解析 → 初始化
加载(Loading)
// 类加载器负责将.class文件加载到内存
// 1. 通过全限定类名获取类的二进制字节流
// 2. 将字节流代表的静态存储结构转换为方法区的运行时数据结构
// 3. 在堆中生成一个java.lang.Class对象作为方法区数据的访问入口
public class ClassLoadingDemo {
public static void main(String[] args) throws ClassNotFoundException {
// 使用指定类加载器加载类
ClassLoader loader = ClassLoadingDemo.class.getClassLoader();
Class<?> clazz = loader.loadClass("com.example.User");
System.out.println(clazz.getName());
}
}
验证(Verification)
确保Class文件的字节流符合JVM规范。
// 验证阶段包括:
// 1. 文件格式验证:是否以0xCAFEBABE开头
// 2. 元数据验证:是否有父类、是否继承了final类
// 3. 字节码验证:方法体是否合法
// 4. 符号引用验证:引用的类/方法/字段是否存在
准备(Preparation)
为类的静态变量分配内存并设置默认值。
public class PrepareDemo {
// 准备阶段:value = 0(默认值)
// 初始化阶段:value = 123
public static int value = 123;
// final修饰的常量在编译期就已确定,准备阶段直接赋值
public static final int CONSTANT = 456;
}
解析(Resolution)
将常量池中的符号引用替换为直接引用。
public class ResolveDemo {
public void method() {
// 符号引用:方法名"method"
// 直接引用:方法在内存中的地址
}
}
初始化(Initialization)
执行类构造器<clinit>()方法。
public class InitDemo {
// 静态变量按顺序执行
public static int a = 10;
public static int b = a + 1; // b = 11
// 静态代码块
static {
System.out.println("类初始化");
}
// 构造器(实例初始化)
{
System.out.println("实例初始化");
}
public InitDemo() {
System.out.println("构造方法");
}
}
触发初始化的情况
// 1. new对象
User user = new User();
// 2. 访问静态字段(final常量除外)
int value = MyClass.VALUE;
// 3. 调用静态方法
MyClass.staticMethod();
// 4. 反射
Class.forName("com.example.MyClass");
// 5. 子类初始化会先初始化父类
// 6. 主类(包含main方法的类)
双亲委派模型
Bootstrap ClassLoader (启动类加载器)
↓
Extension ClassLoader (扩展类加载器)
↓
Application ClassLoader (应用类加载器)
↓
Custom ClassLoader (自定义类加载器)
// 双亲委派机制
public class BootstrapClassLoader {
// 加载Java核心类库(rt.jar等)
// 由C++实现,Java中无法直接获取
}
// Extension ClassLoader
public class ExtClassLoaderDemo {
// 加载Java扩展类库(ext目录)
public static void main(String[] args) {
ClassLoader extLoader = ClassLoader.getSystemClassLoader().getParent();
System.out.println("Extension ClassLoader: " + extLoader);
}
}
// Application ClassLoader
public class AppClassLoaderDemo {
// 加载classpath下的类
public static void main(String[] args) {
ClassLoader appLoader = ClassLoader.getSystemClassLoader();
System.out.println("Application ClassLoader: " + appLoader);
// sun.misc.Launcher$AppClassLoader
}
}
自定义类加载器
public class CustomClassLoader extends ClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
// 将类名转换为文件路径
String path = name.replace('.', '/') + ".class";
InputStream is = getResourceAsStream(path);
if (is == null) {
throw new ClassNotFoundException(name);
}
byte[] data = is.readAllBytes();
return defineClass(name, data, 0, data.length);
} catch (IOException e) {
throw new ClassNotFoundException(name, e);
}
}
public static void main(String[] args) throws Exception {
CustomClassLoader loader = new CustomClassLoader();
Class<?> clazz = loader.loadClass("com.example.User");
System.out.println("类加载器: " + clazz.getClassLoader());
System.out.println("是否同一个类: " + (clazz == com.example.User.class));
}
}
打破双亲委派
SPI机制
// JDBC使用SPI打破双亲委派
// 核心接口在rt.jar(Bootstrap加载)
// 实现在classpath(Application加载)
// 线程上下文类加载器
Thread.currentThread().getContextClassLoader();
热部署
// 实现类的热替换
public class HotSwapClassLoader extends ClassLoader {
private String className;
public HotSwapClassLoader(String className) {
this.className = className;
}
public Class<?> reload() throws ClassNotFoundException {
// 每次都重新加载类
return findClass(className);
}
}
类加载器隔离
// 同一个类被不同类加载器加载,JVM认为是不同的类
public class IsolationDemo {
public static void main(String[] args) throws Exception {
ClassLoader loader1 = new CustomClassLoader();
ClassLoader loader2 = new CustomClassLoader();
Class<?> clazz1 = loader1.loadClass("com.example.User");
Class<?> clazz2 = loader2.loadClass("com.example.User");
System.out.println(clazz1 == clazz2); // false
}
}
总结
类加载机制是JVM的重要组成部分。理解加载过程、双亲委派模型和自定义类加载器,对于实现热部署、模块化和类隔离等高级特性非常有帮助。