类加载全流程
根据 JVM 规范,类生命周期分为以下阶段:
加载(Loading)
将 .class
字节码加载到内存,生成 Class<?>
对象。为数不多应用代码中可以介入插手的阶段。
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
类缓存机制
每个类加载器调用 loadClass()
时,首先通过 findLoadedClass()
(native method)检查本地缓存是否已加载该类,避免重复加载。
双亲委派流程
方法:ClassLoader.loadClass()
→ parent.loadClass()
→ findClass()
向上委派:子类加载器的
loadClass()
优先调用父类加载器的parent.loadClass()
(递归至Bootstrap
)。向下加载:若所有父类均无法加载(返回
null
),子类调用findClass()
自行加载(如AppClassLoader
在classpath
中搜索)。
沙箱保护机制
方法:ClassLoader.loadClass()
的访问控制 + SecurityManager.checkPackageAccess()
双亲委派机制的保护:通过双亲委派,
Bootstrap
优先加载java.*
等核心类,下级比如AppClassLoader
无权加载核心类。权限限制:自定义类加载器尝试加载
java.*
包,defineClass()
会直接抛出SecurityException
(JVM 底层限制)。
SecurityException: Prohibited package name: java.lang
java.lang.SecurityException: Prohibited package name: java.lang
at java.base/java.lang.ClassLoader.preDefineClass(ClassLoader.java:942)
at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1012)
at CustomClassLoader.findClass(CustomClassLoader.java:18)
...
loadClass,findClass 和 defineClass 速记
loadClass
加载类到内存中时,确定用哪一个ClassLoader
加载,是实现双亲委派的关键, 可以覆写从而打破双亲委派。
findClass
findClass
是 ClassLoader
的模板方法,用于 自定义类加载逻辑(如从非标准位置加载类)。
默认行为:
ClassLoader
的findClass
直接抛出ClassNotFoundException
。自定义类加载器 通常会重写
findClass
,在其中:查找类字节码(如从文件、网络、数据库等)。
调用
defineClass
将字节码转换为Class
对象。
defineClass
defineClass
是 ClassLoader
的核心方法,负责将 字节码(byte[]
)转换为 Class
对象。通常在findClass
中调用。
链接(Linking)
链接分为三个子阶段:
验证(Verification)
目标:确保字节码符合 JVM 规范(如类型安全、操作数栈合法性)。
触发时机:在
defineClass
中完成(属于加载阶段的一部分,但规范将其归为链接阶段)。验证内容:
文件格式验证(魔数、版本号)。
元数据验证(继承关系、final 规则)。
字节码验证(方法逻辑、跳转指令)。
准备(Preparation)
目标:为类的静态变量分配内存,并设置默认初始值(如
int
设为0
,引用设为null
)。作⽤是占住内存。然后等连接过程完成后,在后⾯的初始化阶段,再将静态属性从默认值修改为指定的初始值。触发时机:由 JVM 在链接阶段自动处理,无直接方法调用。
示例:
public static int value = 123; // 准备阶段设为 0,初始化阶段设为 123
例外情况 - final
如果这个静态变量是final修饰的,准备阶段直接赋实际值(跳过零值)。
解析(Resolution)
目标:将符号引用(如类名、方法名)转换为直接引用(内存地址或句柄)。
触发方法:
延迟解析:默认由 JVM 在首次实际使用时触发(如调用方法、访问字段)。
显式解析:通过
ClassLoader.resolveClass(Class<?> c)
提前触发。
示例:
classLoader.resolveClass(MyClass.class); // 强制解析
初始化(Initialization)
目标:执行类的静态代码块(
<clinit>
)和静态变量赋值。JVM 准备类的静态环境(一次性的)。触发条件(JVM 严格控制的主动使用场景):
new
实例化对象。访问类的静态变量(非 final)或静态方法。
反射调用(如
Class.forName("MyClass")
)。子类初始化时,父类需先初始化。
核心任务:
执行静态变量赋值(如
static int x = 10;
)。执行静态代码块(
static { ... }
)。生成类的
Class
对象(Class<?>
)。
关键方法:
Class.forName(String name, boolean initialize, ClassLoader loader)
:若initialize=true
会触发初始化。
class Demo {
static {
System.out.println("静态代码块执行(初始化阶段)");
}
static int x = 1; // 静态变量赋值
}
// 触发初始化:
Demo.x = 2; // 访问静态变量
使用(Usage)
目标:正常使用类的实例和静态成员。程序运行时的动态行为(可多次发生)。
核心行为:
创建对象实例(调用构造函数
<init>
)。访问普通成员变量或方法。
通过反射操作类(如调用方法、读写字段)。
触发方式:通过
new
、方法调用、字段访问等。
class Demo {
int y = 10; // 实例变量(初始化阶段不处理)
void print() { System.out.println("方法调用(使用阶段)"); }
}
// 使用阶段:
Demo obj = new Demo(); // 创建实例
obj.print(); // 调用方法
卸载(Unloading)
目标:从内存中移除不再使用的类。
触发条件:
类的所有实例已被回收。
加载该类的
ClassLoader
实例已被回收。无任何地方通过反射持有该类的引用。
由 JVM 的垃圾回收机制自动处理,无直接 API 控制。