Skip to content

双亲委派机制

定义

总结来说,就是子类加载器会先看看自己有没有加载过,加载过就不加载了,没加载过就会让父类加载器加载,这样一层一层往上抛,直到父类找不到的时候再让子类去加载。这样的层次结构保证了类加载的一致性和安全性,避免了类的重复加载和恶意代码的替换。

image-20230306170728531

验证

实验一

java
public class Main {
    public static void main(String[] args) throws ReflectiveOperationException {
        Class<?> testClass1 = Main.class.getClassLoader().loadClass("com.sun.crypto.provider.DESKeyFactory");
				// com.sun.crypto.provider.DESKeyFactory属于扩展类加载器范围的
        CustomClassLoader customClassLoader = new CustomClassLoader();
        Class<?> testClass2 = customClassLoader.loadClass("com.sun.crypto.provider.DESKeyFactory");

        //看看两个类的类加载器是不是同一个
        System.out.println(testClass1.getClassLoader());
        System.out.println(testClass2.getClassLoader());

        //看看两个类是不是长得一模一样
        System.out.println(testClass1);
        System.out.println(testClass2);

        //两个类是同一个吗?
        System.out.println(testClass1 == testClass2);
      
    }

  // 自定义的类加载器,默认继承应用类加载器
    static class CustomClassLoader extends ClassLoader {
        @Override
        public Class<?> loadClass(String name) throws ClassNotFoundException {
            try (FileInputStream stream = new FileInputStream("./target/classes/"+name.replace(".", "/")+".class")){
                byte[] data = new byte[stream.available()];
                stream.read(data);
                if(data.length == 0) return super.loadClass(name);
                return defineClass(name, data, 0, data.length);
            } catch (IOException e) {
                return super.loadClass(name);
            }
        }
    }
}

运行结果:

image-20240420164117063

这证明了当前类加载器会委托上一级类加载器去加载,并且如果是同一个类加载器加载的同一个类,是完全一样的。

实验二

java
package com.test;

public class Test {
}


public class Main {
    public static void main(String[] args) throws ReflectiveOperationException {
        Class<?> testClass1 = Main.class.getClassLoader().loadClass("com.test.Test");

        CustomClassLoader customClassLoader = new CustomClassLoader();
        Class<?> testClass2 = customClassLoader.loadClass("com.test.Test");

        //看看两个类的类加载器是不是同一个
        System.out.println(testClass1.getClassLoader());
        System.out.println(testClass2.getClassLoader());

        //看看两个类是不是长得一模一样
        System.out.println(testClass1);
        System.out.println(testClass2);

        //两个类是同一个吗?
        System.out.println(testClass1 == testClass2);
    }

    static class CustomClassLoader extends ClassLoader {
        @Override
        public Class<?> loadClass(String name) throws ClassNotFoundException {
            try (FileInputStream stream = new FileInputStream("./target/classes/"+name.replace(".", "/")+".class")){
                byte[] data = new byte[stream.available()];
                stream.read(data);
                if(data.length == 0) return super.loadClass(name);
                return defineClass(name, data, 0, data.length);
            } catch (IOException e) {
                return super.loadClass(name);
            }
        }
    }
}

运行结果:

image-20240420164151649

这证明了即使是字节码相同的类,当类加载不同时,也是不相同的类。即两个类来自同一个Class文件并且是由同一个类加载器加载的,才能判断为是同一个。

注:

java
System.out.println(String.class.getClassLoader());
//运行结果为null
// 因为String类位于rt.jar(java运行核心类库)下面,由引导类加载器负责加载,引导类加载器是C++实现的,在Java中获取不到,故该部分为Null

打破

双亲委派机制存在的问题

  • 不灵活:有的时候只想去使用自定义类加载器来加载自己的类,不想去加载父类的。
  • 无法实现类的动态更新:比如热部署功能,在双亲委派机制中被加载过的类是不会重复再加载的。所以如果希望重复加载,就要打破双亲委派机制。

双亲委派机制不强制要求实现,只是推荐使用。可以在继承ClassLoader的时候通过重写findClass方法实现不加载父类加载器。