双亲委派机制
在了解双亲机制之前先要了解一下类加载的生命周期。
按照Java虚拟机规范,从class文件到加载到内存中的类,到类卸载出内存为止,它的整个生命周期包括如下7个阶段:
- 加载:通过
类加载器
将.class文件从磁盘加载到内存中 - 连接
- 2.1 验证:验证字节码文件的正确性
- 2.2 准备:给类的静态变量分配内存,并赋予默认值(虚拟机默认的初始值)
- 2.3 解析:类装载器装入所引用的其他所有类
- 初始化:为类的静态变量赋予正确的初始值(上面的只是默认值,此处赋值是我们所写的真正的初始值),执行静态代码块。
- 使用
- 卸载
在类加载阶段我们提到了,Java源代码被编译器编译成**.class**的字节码文件。然后由我们得ClassLoader(类加载器)负责将这些class文件给加载到JVM中去执行。它是如何被加载到JVM中的呢?
我们看以下ClassLoader的核心的加载类,会发现一个有趣的事情。
java.lang.ClassLoader
public abstract class ClassLoader {
private final ClassLoader parent;
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先,检查该类是否已经被加载
Class<?> c = findLoadedClass(name);
// 类没有被加载才会进入加载类的方法
if (c == null) {
long t0 = System.nanoTime();
try {
// 存在父加载器,递归的交由父加载器
if (parent != null) {
c = parent.loadClass(name, false);
} else {
// 直到最上面的Bootstrap类加载器
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;
}
}
}
这段代码很明确的解释了双亲委派机制,为了大家更容易理解,通过以下流程图来解释更为清楚。
通过以上的流程很容易理解,一个 User.class 文件要被加载时,不考虑我们自定义类加载器,首先会在AppClassLoader中检查是否加载过,如果有那就无需再加载了。如果没有,那么会拿到父加载器,然后调用父加载器的loadClass方法。父类中同理也会先检查自己是否已经加载过,如果没有再往上。注意这个类似递归的过程,直到到达Bootstrap classLoader之前,都是在检查是否加载过,并不会选择自己去加载。直到BootstrapClassLoader,已经没有父加载器了,这时候开始考虑自己是否能加载了,如果自己无法加载,会下沉到子加载器去加载,一直到最底层,如果没有任何加载器能加载,就会抛出ClassNotFoundException。
为什么要设计这种机制?
这种设计有个好处是,如果有人想替换系统级别的类:String.java。篡改它的实现,在这种机制下这些系统的类已经被Bootstrap classLoader加载过了(为什么?因为当一个类需要加载的时候,最先去尝试加载的就是BootstrapClassLoader),所以其他类加载器并没有机会再去加载,从一定程度上防止了危险代码的植入。