什么是双亲委派机制?
- 当某个类加载器需要加载某个
.class
文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类。
类加载器有哪几种?
- BootstrapClassLoader(启动类加载器)
c++
编写,加载java
核心库java.*
,构造ExtClassLoader
和AppClassLoader
。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作 - ExtClassLoader (标准扩展类加载器)
java
编写,加载扩展库,如classpath
中的jre
,javax.*
或者java.ext.dir
指定位置中的类,开发者可以直接使用标准扩展类加载器。主要负责加载jre/lib/ext
目录下的一些扩展的jar
- AppClassLoader(系统类加载器)
java
编写,加载程序所在的目录,如user.dir
所在的位置的class
- CustomClassLoader(用户自定义类加载器)
java
编写,用户自定义的类加载器,可加载指定路径的class
文件
咱们来看一下源代码
/**
* 上面那张图是原版的注释,翻译后为以下内容
*
* 用指定的二进制名称加载类。 此方法的默认实现按以下顺序搜索类:
* 1.调用 findLoadedClass 来检查一下这个类是否已经被加载了
* 2.在父加载器上面调用 loadClass 方法,如果父加载器为空,取而代之的是使用虚拟机内置的类加载器
* 3.调用 findClass 方法去寻找这个类
* 如果这个类通过以上的步骤找到了,并且 resolve 标志为 true ,
* 则此方法将在生成的 Class 对象上调用 resolveClass 方法 (resolve即解析的意思)
* 鼓励 ClassLoader 的子类重写 findClass ,而不是此方法。
* 除非被覆盖,否则此方法将在整个类加载过程中同步 getClassLoadingLock 方法的结果
*/
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;
}
}
为了更容易理解上面的代码,用一张图来描述一下:
当一个.class
这样的文件要被加载时。先假设没有自定义类加载器,那么就会首先会在AppClassLoader
中检查是否加载过,如果有那就无需再加载了。如果没有,那么会去到父加载器,然后调用父加载器的loadClass
方法。父加载器中同理会先检查自己是否已经加载过,如果没有再往上去找。
注意这个过程,在到达BootstrapClassLoader
之前,都是没有哪个加载器会自己去加载的。而是当父加载器无法加载的时候,才会下沉到子加载器去加载,一直到最底层,如果没有任何加载器能加载,就会抛出ClassNotFoundException
异常。
双亲委派机制的作用
- 防止重复加载同一个
.class
。通过委托去向上面问一问,加载过了,就不用再加载一遍。保证数据安全。 - 保证核心
.class
不能被篡改。通过委托方式,不会去篡改核心.class
,即使篡改也不会去加载,即使加载也不会是同一个.class
对象了。不同的加载器加载同一个.class
也不是同一个Class对象
。这样保证了Class执行安全。 - 举个例子:如果有人写了一个类:String.java,而且还是建在自己项目中自己写的
java.lang
包下(注意,这里不是指卸载JDK的包,而是自己创建的同名的包),那么在这种机制下,这些系统的类会已经被BootstrapClassLoader
加载过了,所以并不会再去加载,从一定程度上防止了危险代码的植入。
咱们来写个测试类测试一下吧
沙箱安全机制
- 沙箱安全机制即防止恶意代码污染java源代码
- 沙箱机制就是将 Java 代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制代码对本地系统资源访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏。
- 上面例子中,定义了一个名为
String
的类且所在包为java.lang
- 因为这个类本来是属于
jdk
的,如果没有沙箱安全机制的话,这个类将会污染到我所有的String
- 但是由于
沙箱安全机制
,所以就委托顶层的Bootstrap引导类加载器
查找这个类 - 如果没有的话就委托
ExtsionClassLoader扩展类加载器
,ExtsionClassLoader
没有就到AppClassLoader应用类加载器
- 由于
String
就是jdk
的源代码,所以在Bootstrap
那里就加载到了,先找到先使用,所以就使用Bootstrap
里面的String
,后面的一概不能使用,这就保证了不被恶意代码污染
总结
一句话概括就是
- 通过双亲委派机制找到加载这个类的加载器;然后再通过沙箱安全机制来执行加载这个类