29
2020
11

Java类加载体系之ClassLoader双亲委托机制

java是一种类型安全的语言,它有四类称为安全沙箱机制的安全机制来保证语言的安全性,这四类安全沙箱分别是:

 

1.类加载体系

 

2.class文件检验器

 

3.内置于Java虚拟机(及语言)的安全特性

 

4.安全管理器及Java API

 

● 主要讲解类的加载体系:

 

java程序中的.java文件编译完会生成.class文件,而.class文件就是通过被称为类加载器的ClassLoader加载的,而ClassLoder在加载过程中会使用“双亲委派机制”来加载.class文件,图:

 

1558327102@7324ea804022678b5911eaad54e39052.png

 

BootStrapClassLoader:启动类加载器,该ClassLoader是jvm在启动时创建的,用于加载$JAVA_HOME$/jre/lib下面的类库(或者通过参数-Xbootclasspath指定)。由于启动类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不能直接通过引用进行操作。

 

ExtClassLoader:扩展类加载器,该ClassLoader是在sun.misc.Launcher里作为一个内部类ExtClassLoader定义的(即sun.misc.Launcher$ExtClassLoader),ExtClassLoader会加载$JAVA_HOME/jre/lib/ext下的类库(或者通过参数-Djava.ext.dirs指定)。

 

AppClassLoader:应用程序类加载器,该ClassLoader同样是在sun.misc.Launcher里作为一个内部类,AppClassLoader定义的(即sun.misc.Launcher$AppClassLoader),AppClassLoader会加载java环境变量CLASSPATH所指定的路径下的类库,而CLASSPATH所指定的路径可以通过System.getProperty("java.class.path")获取;当然,该变量也可以覆盖,可以使用参数-cp,例如:java-cp路径(可以指定要执行的class目录)。

 

CustomClassLoader:自定义类加载器,该ClassLoader是指我们自定义的ClassLoader,比如tomcat的StandardClassLoader属于这一类;当然,大部分情况下使用AppClassLoader就足够了。

 

前面谈到了ClassLoader的几类加载器,而ClassLoader使用双亲委派机制来加载class文件的。ClassLoader的双亲委派机制是这样的(这里先忽略掉自定义类加载器CustomClassLoader):

 

1.当AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。

 

2.当ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。

 

3.如果BootStrapClassLoader加载失败(例如在$JAVA_HOME$/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载;

 

4.若ExtClassLoader也加载失败,则会使用AppClassLoader来加载,如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException。

 

下面贴下ClassLoader的loadClass(String name,boolean resolve)的源码:

 

protected synchronized Class<?> loadClass(String name,boolean resolve)throws ClassNotFoundException{

        // 首先找缓存是否有 class

        Class c=findLoadedClass(name);

        if(c==null){

        //没有判断有没有父类

        try{

        if(parent!=null){

        //有的话,用父类递归获取 class

        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){

        // 如果还是没有找到,调用 findClass(name)去找这个类

        c=findClass(name);}

        }

        if(resolve){

        resolveClass(c);}

        return c;

        }

 

代码很明朗:

 

首先找缓存(findLoadedClass),没有的话就判断有没有parent,有的话就用parent来递归的loadClass,然而ExtClassLoader并没有设置parent,则会通过findBootstrapClassOrNull来加载class,而findBootstrapClassOrNull则会通过JNI方法”private native Class findBootstrapClass(String name)”来使用BootStrapClassLoader来加载class。

 

如果parent未找到class,则会调用findClass来加载class,findClass是一个protected的空方法,可以覆盖它以便自定义class加载过程。另外,虽然ClassLoader加载类是使用loadClass方法,但是鼓励用ClassLoader的子类重写findClass(String),而不是重写loadClass,这样就不会覆盖了类加载默认的双亲委派机制。

 

双亲委派托机制为什么安全:

 

举个例子,ClassLoader加载的class文件来源很多,比如编译器编译生成的class、或者网络下载的字节码。而一些来源的class文件是不可靠的,比如我可以自定义一个java.lang.Integer类来覆盖jdk中默认的Integer类,例如下面这样:

 

package java.lang;

public class Integer {

    public Integer(int value) {

        System.exit(0);

    }

}

 

初始化这个Integer的构造器是会退出JVM,破坏应用程序的正常进行,如果使用双亲委派机制的话该Integer类永远不会被调用,以为委托BootStrapClassLoader加载后会加载JDK中的Integer类而不会加载自定义的这个,可以看下下面这测试个用例:

 

public static void main(String...args){

        Integer i=new Integer(1);

        System.err.println(i);

}

 

执行时JVM并未在new Integer(1)时退出,说明未使用自定义的Integer,于是就保证了安全性。

 

« 上一篇 下一篇 »

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。