分类
- 启动类加载器(Bootstrap Class Loader)
- 加载 Java 核心库,位于
<JAVA_HOME>\lib目录,或被-Xbootclasspath指定的路径中存放的能够被 JVM 识别的类(名称为 rt.jar、tools.jar 等,名字不符合的类库机制放在 lib 目录中也不会被加载) - 启动类加载器无法被 Java 程序直接引用,有以 null 值来代表引导类加载器的约定规定
- 加载 Java 核心库,位于
- 扩展类加载器(Extension Class Loader)
- 体现在
sun.misc.Launcher.$ExtClassLoader上,是一种 Java 系统类库的扩展机制 - 加载
<JAVA_HOME>\lib\ext目录,或者被java.ext.dirs系统变量指定的路径中的类库
- 体现在
- 应用程序类加载器(Application Class Loader)
- 体现在
sun.misc.Launcher.$AppClassLoader上,是ClassLoader.getSystemClassLoader()方法的返回值,所以,又称 “系统类加载器” - 加载用户类路径
-classpath参数指定的类库
- 体现在
双亲委派模型
“双亲委派模型” 用来形容 ClassLoader 之间的关系:除了顶层的启动类加载器外,其余的类加载器都应有自己的父类加载器。
优点
保护程序的安全,防止核心 API 被随意篡改(这种保护,也被看作“沙箱安全机制”)。
如:java.lang.Object 在 rt.jar 中,无论哪个类加载器加载这个类,最终都委派给 Bootstrap ClassLoader 进行加载,因此 Object 类在程序的各种类加载器环境中能够保证是一个类。
破坏
双亲委派模型在 JDK 1.2 时被引入,是 Java 推荐的一种类加载器的最佳实践,并不具有强制性约束力,但在一些场景中,也会根据需求来打破这个模式,来实现必要的功能,如:
- Java SPI
- OSGi 模块热部署、代码热替换
- Tomcat
工作过程
- (自下而上请求)当一个类加载器收到了类加载的请求,首先不会自己尝试加载,而是把这个请求委派给父类加载器去完成,
- (从上往下加载)只有当父加载器的搜索范围中没有找到这个类时,子类加载器才会尝试自己去完成加载。
实现原理
- 使用组合关系来复用父加载器的代码
- 在
java.lang.ClassLoader#loadClass(java.lang.String, boolean)中,实现双亲委派模型
在 parent.loadClass(name, false); 中请求父加载器进行加载,当加载失败时(c == null),由当前类加载器使用 c = findClass(name); 进行加载。
Tips:
- 建议重写
loadClass实现自定义类加载器,优雅的做法是将自定义类加载逻辑写在findClass中; - 如果没有特别复杂的需求,可继承
URLClassLoader,少些一些逻辑。
自定义类加载器
自定义类加载器的动机是什么?
- 实现类的隔离,如:Tomcat
- 破坏双亲委派模型
- 扩展加载源,如:从 MySQL 中加载 jar
- 代码加密,防止源码泄露
参考
- 《深入理解Java虚拟机》
- Java 8 源码