Featured image of post 双亲委派机制

双亲委派机制

双亲委派机制

在了解双亲机制之前先要了解一下类加载的生命周期。

按照Java虚拟机规范,从class文件到加载到内存中的类,到类卸载出内存为止,它的整个生命周期包括如下7个阶段:

  1. 加载:通过类加载器将.class文件从磁盘加载到内存中
  2. 连接
    • 2.1 验证:验证字节码文件的正确性
    • 2.2 准备:给类的静态变量分配内存,并赋予默认值(虚拟机默认的初始值)
    • 2.3 解析:类装载器装入所引用的其他所有类
  3. 初始化:为类的静态变量赋予正确的初始值(上面的只是默认值,此处赋值是我们所写的真正的初始值),执行静态代码块。
  4. 使用
  5. 卸载

image-20220412204703927
image-20220412204703927

在类加载阶段我们提到了,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;
        }
    }
}

这段代码很明确的解释了双亲委派机制,为了大家更容易理解,通过以下流程图来解释更为清楚。

image-20220413141738545
image-20220413141738545

通过以上的流程很容易理解,一个 User.class 文件要被加载时,不考虑我们自定义类加载器,首先会在AppClassLoader中检查是否加载过,如果有那就无需再加载了。如果没有,那么会拿到父加载器,然后调用父加载器的loadClass方法。父类中同理也会先检查自己是否已经加载过,如果没有再往上。注意这个类似递归的过程,直到到达Bootstrap classLoader之前,都是在检查是否加载过,并不会选择自己去加载。直到BootstrapClassLoader,已经没有父加载器了,这时候开始考虑自己是否能加载了,如果自己无法加载,会下沉到子加载器去加载,一直到最底层,如果没有任何加载器能加载,就会抛出ClassNotFoundException。

为什么要设计这种机制?

这种设计有个好处是,如果有人想替换系统级别的类:String.java。篡改它的实现,在这种机制下这些系统的类已经被Bootstrap classLoader加载过了(为什么?因为当一个类需要加载的时候,最先去尝试加载的就是BootstrapClassLoader),所以其他类加载器并没有机会再去加载,从一定程度上防止了危险代码的植入。

Licensed under CC BY-NC-SA 4.0