JVM的双亲委派机制
什么是双亲委派机制
作用
JVM加载类的过程中需要使用类加载器进行加载, 而在Java中类加载器有很多, 双亲委派机制就是用来指定哪个类加载器来加载类.
Java中的类加载器
- Bootstrap ClassLoader 启动类加载器
- Extention ClassLoader 标准扩展类加载器
- Application ClassLoader 引用类加载器
- User ClassLoader 用户自定义类加载器
各个类加载器之间的层次关系
- 上层类加载器是下一层加载器的父加载器
定义
双亲委派机制: 当一个类加载器收到了类加载请求时, 它不会直接加载该类, 而是将请求委托给自己的父加载器(上层)去加载. 只有父加载器无法加载该类时, 才会由当前加载器负责该类的加载.
什么情况下父加载器无法加载一个类?
Java中的类加载器有各自的作用, 当类超出作用范围时, 该类加载器无法加载该类.
下面描述各类加载器的职责:
-
BootstrapClassLoder(启动类加载器):
- Java核心类库
-
%JRE_HOME%/lib
路径下的rt.jar, resources.jar, charsets.jar, class - ...
-
Extention ClassLoder(标准扩展类加载器)
-
%JRE_HOME%/lib/ext
路径下的jar包和class文件
-
-
Application ClassLoader(引用类加载器)
- 加载当前应用的classpath下的类
-
User ClassLoader(用户自定义类加载器)
- 可以加载指定路径下的class文件
也就是说, 用户自定义的类, 比如: com.hollis.ClassHollis不会被Bootstrap和Extention加载器加载.(因为如果是已指定路径下被User加载, 如果没有则会被Application加载.)
为什么需要双亲委派
由于类加载器之间有严格的层次关系, 那也使得类本身也具有了层次关系.(就好比学生在专业上划分了计算机, 哲学..., 而在年纪上也划分了大一大二..., 类本身从包路径划分了层次关系, 而类加载器又从另一个角度划分了类的层次关系)
这种层次关系是优先级.
加载实例
- 一个java.lang包下的类, 存储来re.jar中, 所以会被一路委派到Bootstrap类加载器中加载
- 一个用户自定的com.hollis.ClassHollis类, 会被一路委派到Bootstrap类加载器, Bootstrap加载不了该类, 则交给Extention, Extention加载不了, 则交给Application处理, 最终Application类加载器加载了该类.
好处
- 通过委派的方式, 避免了类重复加载: 父加载器加载后, 子加载器不会重复加载
- 通过双亲委派方式, 保证安全性: Bootstrap ClassLoader加载时, 只加载JAVA_HOME下jar包中的类, 这个类是本地的, 不会被人替代, 除非有人直接修改了本地JDK. (假设没有双亲委派机制, 只有一个类加载器, 这时候有人自定了一个java.lang包中的类, 由于只有唯一的类加载器, 自定的java.lang类也交给了该类加载器, 就造成了API篡改)
父子加载器之间的关系
- 父子加载器并不是继承的关系
- 父子加载器使用的是组合的关系, 来复用父加载器代码
第二点的组合关系(关于组合关系的描述见聚集和组合), 如下所示:
public abstract calss ClassLoader {
// The parent class loader for delegation
private final ClassLoader parent;
}
双亲委派实现
所有双亲委派的代码集中在java.lang.ClassLoader的loadClass()方法中:
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(); // 第一次调用设置起始时间T
try {
if (parent != null) { // 如果该类有父加载器, 则优先交给父加载器加载该类
c = parent.loadClass(name, false); // 又调用了父类的loadClass()方法
} else { // 如果没有父加载器, 则已经是顶层类加载器(Bootstrap), 交给Bootstrap加载器看是否可以加载
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
// 如果抛出该异常, 说明从非空的父加载器中未找到该类
}
if (c == null) { // 如果c仍是null, 说明类仍未加载, 有两种情况: 1. 父加载器不能加载; 2. Bootstrap不能加载
// If still not found, then invoke findClass in order to find the class.
// 如果仍未发现该类, 则调用findClass()去找到该类
long t1 = System.nanoTime(); // T + t (t为运行时长)
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方法中实现的, 如要破坏, 则: 自定义一个类加载器, 重写其中的loadClass方法, 使其不进行双亲委派即可.
loadClass(), findClass(), defineClass()区别
区别如下:
- loadClass(): 主要进行类加载的方法, 默认的双亲委派机制在其中实现
- finClass(): 根据名称或位置加载.class字节码
- defineClass(): 将字节码转化为Class