双亲委派机制

双亲委派机制

一、什么是双亲委派机制

每个类加载器都有自己的一个加载路径,当某个类加载器需要加载某个.class文件时,它不是立刻从自己的加载路径中去找这个class文件,而是委派给它的父类加载器去加载。它的父类加载器又委派给父类的父类加载器去加载。

双亲委派机制_第1张图片

 

Class Loader的作用是,将静态的class文件加载成Class对象,加入jvm

 

1、Bootstrap ClassLoader

public static void bootstrapClassLoaderTest(){
        System.out.println(Main.class.getClassLoader().getParent().getParent());
        //打印加载路径
        System.out.println(System.getProperty("sun.boot.class.path"));
    }

输出结果如下:

null D:\jdk1.8\jre\lib\resources.jar;D:\jdk1.8\jre\lib\rt.jar;D:\jdk1.8\jre\lib\sunrsasign.jar;D:\jdk1.8\jre\lib\jsse.jar;D:\jdk1.8\jre\lib\jce.jar;D:\jdk1.8\jre\lib\charsets.jar;D:\jdk1.8\jre\lib\jfr.jar;D:\jdk1.8\jre\classes

null,代表bootstrapClassLoader,它是用c++写的。在java找不到它的引用,所以这里为空

 

Bootstrap ClassLoader是根类加载器,是最顶层的加载器,没有父类加载器,主要负责虚拟机核心类库的加载。其加载路径为sun.boot.class.path,大致是$JAVA_HOME/jre/lib下面的rt.jar、charsets.jar和classes等。启动jvm,可以通过指定-Xbootclasspath,来改变Bootstrap ClassLoader的加载路径:java -Xbootclasspath:c:/path1/jar1.jar;c:/path2/jar2.jar Test

 

2、Ext ClassLoader

public static void extClassLoaderTest(){
        System.out.println(Main.class.getClassLoader().getParent());
        System.out.println(System.getProperty("java.ext.dirs"));
    }

输出结果如下:

sun.misc.Launcher$ExtClassLoader@1b6d3586 D:\jdk1.8\jre\lib\ext;C:\windows\Sun\Java\lib\ext

 

Ext ClassLoader为拓展类加载器,它的父类加载器是Bootstrap ClassLoader,其加载路径为java.ext.dirs,也就是$JAVA_HOME/jre/lib/ext目录

 

3、App ClassLoader

public static void appClassLoaderTest(){
        System.out.println(Main.class.getClassLoader());
        System.out.println(System.getProperty("java.class.path"));
    }

输出结果如下:

sun.misc.Launcher$AppClassLoader@18b4aac2 D:\jdk1.8\jre\lib\charsets.jar;D:\jdk1.8\jre\lib\deploy.jar;D:\jdk1.8\jre\lib\ext\access-bridge-64.jar;D:\jdk1.8\jre\lib\ext\cldrdata.jar;D:\jdk1.8\jre\lib\ext\dnsns.jar;D:\jdk1.8\jre\lib\ext\jaccess.jar;D:\jdk1.8\jre\lib\ext\jfxrt.jar;D:\jdk1.8\jre\lib\ext\localedata.jar;D:\jdk1.8\jre\lib\ext\nashorn.jar;D:\jdk1.8\jre\lib\ext\sunec.jar;D:\jdk1.8\jre\lib\ext\sunjce_provider.jar;D:\jdk1.8\jre\lib\ext\sunmscapi.jar;D:\jdk1.8\jre\lib\ext\sunpkcs11.jar;D:\jdk1.8\jre\lib\ext\zipfs.jar;D:\jdk1.8\jre\lib\javaws.jar;D:\jdk1.8\jre\lib\jce.jar;D:\jdk1.8\jre\lib\jfr.jar;D:\jdk1.8\jre\lib\jfxswt.jar;D:\jdk1.8\jre\lib\jsse.jar;D:\jdk1.8\jre\lib\management-agent.jar;D:\jdk1.8\jre\lib\plugin.jar;D:\jdk1.8\jre\lib\resources.jar;D:\jdk1.8\jre\lib\rt.jar;E:\workspace\apache-tomcat-8.0.11-src\test\out\production\test;D:\IntelliJ IDEA 2019.3.1\lib\idea_rt.jar

 

App ClassLoader为应用程序类加载器,也叫系统类加载器,负责加载当前应用类路径下的所有类。其加载路径为java.class.path,也就是$CLASSPATH。可是我自己电脑操作系统的环境变量CLASSPATH为.;%JAVA_HOME%\lib;%JAVA_HOME%\lib\tools.jar;%ANT_HOME%\lib。(JAVA_HOME为D:\JDK1.8)这与上面的输出结果不一致吧,为什么呢?因为IntelliJ IDEA 在工程中重新配置了依赖包,相当于将系统属性java.class.path的值,重新配置了一遍

 

双亲委派机制_第2张图片

 

二、双亲委派的源码分析

你怎么知道双亲委派机制就是上面描述的那样?有证据吗?证据就在ClassLoader#loadClass(java.lang.String)

//ClassLoader#loadClass(java.lang.String)
public Class loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }
//ClassLoader#loadClass(java.lang.String, boolean)
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 {
                        //如果父类加载器为空,则说明递归到BootstrapClassLoader。BootstrapClassLoader是用c++写的,在java上找不到它的引用,所以为空。
                        //委派给BootstrapClassLoader去加载
                        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;
        }
    }

 

三、自定义类加载器

所有的自定义类加载器,都是ClassLoader的直接子类或间接子类。java.lang.ClassLoader是一个抽象类,它里面没有抽象方法,但是有findClass方法,子类必须要实现该方法,不然会抛出ClassNotFoundException

//ClassLoader#findClass
protected Class findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }
public class CustomizeClassLoader extends ClassLoader {
    private final static Path DEFAULT_CLASS_DIR = Paths.get("E:", "classloader1");
​
    private final Path classDir;
​
    public CustomizeClassLoader() {
        super();
        this.classDir = DEFAULT_CLASS_DIR;
    }
​
​
    /**
     * 指定加载路径
     *
     * @param classDir
     */
    public CustomizeClassLoader(String classDir) {
        super();
        this.classDir = Paths.get(classDir);
    }
​
    /**
     * 指定加载路径的同时,指定父类加载器
     *
     * @param parent
     * @param classDir
     */
    public CustomizeClassLoader(ClassLoader parent, String classDir) {
        super(parent);
        this.classDir = Paths.get(classDir);
    }
​
​
    @Override
    protected Class findClass(String name) throws ClassNotFoundException {
        //读取class的二进制数据
        byte[] classBytes = this.readClassBytes(name);
        //如果数据为null,或者没有读到任何信息,则抛出ClassNotFoundException异常
        if(null == classBytes || classBytes.length == 0){
            throw new ClassNotFoundException("Can not load the class " + name);
        }
​
        //调用defineClass方法定义class
        return this.defineClass(name, classBytes, 0, classBytes.length);
    }
​
    private byte[] readClassBytes(String name) throws ClassNotFoundException {
        //将包名分隔符转换为文件路径分隔符
        String classpath = name.replace(".", "/");
        Path classFullPath = classDir.resolve(Paths.get(classpath + ".class"));
        if(!classFullPath.toFile().exists()){
            throw new ClassNotFoundException("The class " + name + " not found.");
        }
​
        try(ByteArrayOutputStream baos = new ByteArrayOutputStream()){
            Files.copy(classFullPath, baos);
            return baos.toByteArray();
        } catch (IOException e) {
            throw new ClassNotFoundException("load the class " + name + " occur error.", e);
        }
    }
}

CustomizeClassLoader可以加载E:\classloader1目录下的.class文件,如果刚巧这个项目的CLASSPATH就是E:\classloader1,那么CustomizeClassLoader还能加载到.class文件吗?不能,由双亲委派机制可知,AppClassLoader会先加载到。那么有没有什么办法,让CustomizeClassLoader加载到.class文件呢?有两种办法可以做到:

1、绕过AppClassLoader,将拓展类加载器或跟类加载器作为父类

ClassLoader extClassLoader = Main.class.getClassLoader().getParent();
CustomizeClassLoader customizeClasLoader = new CustomizeClassLoader(extClassLoader, "E:\\classloader1");

2、破坏双亲委派机制,重写loadClass方法,先进行自定义类加载器的加载

/**
 * @author: create by Rhine
 * @date:2020/7/28 20:57
 * @description:
 */
public class BrokerDelegateClassLoader extends ClassLoader {
    ...
    
    @Override
    protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
        synchronized (getClassLoadingLock(name)) {
            //从当前类加载器的已加载类缓存中,根据类的全路径查询类是否已经加载过
            Class c = findLoadedClass(name);
            //如果不在当前类加载器的已加载类缓存中
            if (c == null) {
                //如果类的全路径以java或javax开头,那么直接委派给系统类加载器加载
                if (name.startsWith("java.") || name.startsWith("javax")) {
                    try {
                        c = getSystemClassLoader().loadClass(name);
                    } catch (Exception e) {
                        //ignore
                    }
                } else {
                    //自定义类加载器,先进行加载
                    try {
                        c = findClass(name);
                    } catch (Exception e) {
                        //ignore
                    }
​
                    //如果自定义类加载器,没有加载到
                    if (c == null) {
                        if (getParent() != null) {
                            //如果有父加载器,那么委派给父加载器去加载
                            c = getParent().loadClass(name);
                        } else {
                            //如果没有父加载器,那么委派给系统类加载器去加载
                            c = getSystemClassLoader().loadClass(name);
                        }
                    }
                }
​
                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    throw new ClassNotFoundException("The class " + name + " not found.");
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }
    
    ...
}

 

四、类加载器之间的关系

我们知道BootstrapClassLoader、ExtClassLoader、AppClassLoader之间并不存在继承关系,所谓的父类加载器,只是个parent属性而已。那么这个parent属性,是在哪里设置进去的呢?JVM在启动时,会执行sun.misc.Launcher 类。在该类中,创建一个Extention ClassLoader 扩展类加载器,和一个应用类加载器AppClassLoader

//sun.misc.Launcher#Launcher
public Launcher() {
        Launcher.ExtClassLoader var1;
        try {
            //创建ExtClassLoader,其parent属性为null
            var1 = Launcher.ExtClassLoader.getExtClassLoader();
        } catch (IOException var10) {
            throw new InternalError("Could not create extension class loader", var10);
        }
​
        try {
            //创建AppClassLoader,其parent属性为ExtClassLoader
            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        } catch (IOException var9) {
            throw new InternalError("Could not create application class loader", var9);
        }
​
        ...
​
    }

 

五、双亲委派机制的作用

  • 防止重复加载同一个class。源码里面的Class c = findLoadedClass(name)就能证明

  • 防止核心class不被篡改,比如说如果你自己写一个java.lang.String类,那么由于双亲委派机制,加载到还是rt.jar中的java.lang.String类

 

六、参考资料

  • 《java高并发编程详解 多线程与架构设计》第10章JVM类加载器

  • https://www.cnblogs.com/crazymakercircle/p/9824111.html

  • https://www.jianshu.com/p/1e4011617650

 

 

你可能感兴趣的:(jvm)