一、类加载过程

类从加载到虚拟机到卸载,经历以下阶段:

加载

将.class文件加载到内存,创建Class对象。

加载方式:

  • 从本地文件系统加载
  • 从网络获取
  • 从zip包读取
  • 运行时计算生成

连接

验证:

  • 文件格式验证
  • 元数据验证
  • 字节码验证
  • 符号引用验证

准备:为类变量分配内存并设置初始值。

解析:将符号引用替换为直接引用。

初始化

执行类构造器方法,初始化类变量和执行静态代码块。

二、类加载器

启动类加载器

Bootstrap ClassLoader,负责加载Java核心类库。

加载路径:

  • JAVA_HOME/lib/rt.jar
  • JAVA_HOME/lib/resources.jar
  • JAVA_HOME/lib/sunrt.jar

扩展类加载器

Extension ClassLoader,负责加载扩展类库。

加载路径:

  • JAVA_HOME/lib/ext目录

应用类加载器

Application ClassLoader,负责加载用户类路径上的类。

加载路径:

  • classpath路径

自定义类加载器

继承ClassLoader类,实现findClass方法。

用途:

  • 从非标准来源加载类
  • 实现类隔离
  • 实现热部署

三、双亲委派模型

模型结构

类加载器之间的父子关系:

1
2
3
4
5
6
7
启动类加载器

扩展类加载器

应用类加载器

自定义类加载器

工作流程

类加载器收到加载请求时:

  1. 委托父类加载器加载
  2. 父类加载器无法加载时,自己尝试加载

优势

安全性:

  • 核心类库不会被自定义类覆盖
  • 防止恶意代码替换核心类

一致性:

  • 同一个类由同一个加载器加载
  • 避免类重复加载

四、双亲委派的实现

ClassLoader的loadClass方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
protected Class<?> loadClass(String name, boolean resolve) {
synchronized (getClassLoadingLock(name)) {
// 检查是否已加载
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
// 委托父加载器
c = parent.loadClass(name, false);
} else {
// 委托启动类加载器
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
}

if (c == null) {
// 自己加载
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}

五、打破双亲委派模型

何时需要打破

  • 需要加载不同版本的同名类
  • 实现类的隔离和热部署
  • SPI机制(JDBC等)

实现方式

重写loadClass方法,不委托父加载器:

1
2
3
4
5
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
// 自定义加载逻辑
return findClass(name);
}

线程上下文类加载器

SPI机制使用线程上下文类加载器加载接口实现类:

1
Thread.currentThread().getContextClassLoader()

六、Tomcat类加载机制

Tomcat自定义类加载器实现Web应用隔离:

1
2
3
4
5
6
7
CommonClassLoader:共享类库

CatalinaClassLoader:Tomcat自身类

SharedClassLoader:Web应用共享类

WebAppClassLoader:每个Web应用独立

优势:

  • Web应用之间类隔离
  • 支持不同版本的同名类
  • 支持热部署

七、总结

类加载机制要点:

  • 类加载过程:加载、连接、初始化
  • 三层类加载器:启动、扩展、应用
  • 双亲委派模型保证安全性和一致性
  • 特殊场景可打破双亲委派

理解类加载机制有助于解决类冲突、实现模块化和热部署。