黑马程序员技术交流社区

标题: 【上海校区】深入JVM虚拟机-双亲委派机制 [打印本页]

作者: 小影姐姐    时间: 2018-5-23 09:48
标题: 【上海校区】深入JVM虚拟机-双亲委派机制

        理解双亲委派机制对Java初学者理解Java类加载过程会有很大帮助.

1. 什么是类加载器
        类加载器是一个用来加载类文件的类。
        Java源代码通过javac编译器编译成类文件,然后JVM来执行类文件中的字节码来执行程序。类加载器负责加载文件系统、网络或其他来源的类文件。
        有三种默认使用的类加载器:Bootstrap类加载器、Extension类加载器和System类加载器(或者叫作Application类加载器)。每种类加载器都有设定好从哪里加载类。

1.1 启动(Bootstrap)类加载器
                是用本地代码实现的类装入器,它负责将 /lib下面的类库加载到内存中(比如rt.jar)。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。

1.2 扩展(Extension)类加载器

        是由 Sun 的 ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的。它负责将< Java_Runtime_Home >/lib/ext或者由系统变量 java.ext.dir指定位置中的类库加载到内存中。开发者可以直接使用标准扩展类加载器。

1.3 系统(System)类加载器
        是由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。它负责将系统类路径(CLASSPATH)中指定的类库加载到内存中。开发者可以直接使用系统类加载器。


2. 类加载器的工作原理
2.1 关于Bootstrap

         Java虚拟机的第一个类加载器是Bootstrap,这个加载器很特殊,它不是Java类,因此它不需要被别人加载,它嵌套在Java虚拟机内核里面,也就是JVM启动的时候Bootstrap就已经启动,它是用C++写的二进制代码(不是字节码),它可以去加载别的类。

        这也是我们在测试时为什么发现System.class.getClassLoader()结果为null的原因,这并不表示System这个类没有类加载器,而是它的加载器比较特殊,是BootstrapClassLoader,由于它不是Java类,因此获得它的引用肯定返回null。

2.2 三种类加载之间的关系
        首先使用代码观察一下:

    public class TestClassLoader {
        public static void main(String[] args) {
            ClassLoader loader = TestClassLoader.class.getClassLoader();
            System.out.println(loader.toString());
            System.out.println(loader.getParent().toString());
            System.out.println(loader.getParent().getParent());
        }
    }

        打印结果:
    sun.misc.Launcher$AppClassLoader@500c05c2
    sun.misc.Launcher$ExtClassLoader@454e2c9c
    null

        第一行打印的是应用程序类加载器(系统类加载器), 第二行打印的是其父类加载器,扩展类加载器,按照我们的想法第三行应该打印启动类加载器的,这里却返回的null,原因是上面所说的:Bootstrap不是用java写的,所以返回null.
        用代码表示已经很清楚了,我们可以画张图总结一下:

         

                如上图所示的类加载器之间的这种层次关系,就称为类加载器的双亲委派模型(Parent Delegation Model)。该模型要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。子类加载器和父类加载器不是以继承(Inheritance)的关系来实现,而是通过组合(Composition)关系来复用父加载器的代码。

2.3 双亲委派机制
2.3.1 工作流程
        如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的加载器都是如此,因此所有的类加载请求都会传给顶层的启动类加载器,只有当父加载器反馈自己无法完成该加载请求(该加载器的搜索范围中没有找到对应的类)时,子加载器才会尝试自己去加载。

        在rt.jar包中的java.lang.ClassLoader类中,我们可以查看类加载实现过程的代码,具体源码如下:
    protected synchronized Class loadClass(String name, boolean resolve)  
             throws ClassNotFoundException {  
         // 首先检查该name指定的class是否有被加载  
         Class c = findLoadedClass(name);  
        if (c == null) {  
             try {  
                if (parent != null) {                                                              // 如果parent不为null,则调用parent的loadClass进行加载  
                     c = parent.loadClass(name, false);  
                 } else {  
                    // parent为null,则调用BootstrapClassLoader进行加载  
                     c = findBootstrapClass0(name);  
                 }  
             } catch (ClassNotFoundException e) {  
                 // 如果仍然无法加载成功,则调用自身的findClass进行加载  
                 c = findClass(name);  
             }  
         }  
         if (resolve) {  
             resolveClass(c);  
         }  
         return c;  
    }  

        通过上面代码可以看出,双亲委派模型是通过loadClass()方法来实现的,根据代码以及代码中的注释可以很清楚地了解整个过程其实非常简单:先检查是否已经被加载过,如果没有则调用父加载器的loadClass()方法,如果父加载器为空则默认使用启动类加载器作为父加载器。如果父类加载器加载失败,则先抛出ClassNotFoundException,然后再调用自己的findClass()方法进行加载。        

2.3.2  委派机制的好处
        使用双亲委派模型来组织类加载器之间的关系的好处是: Java类随着它的类加载器一起具备了一种带有优先级的层次关系。

        例如java.lang.Object类,无论哪个类加载器去加载该类,最终都是由启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。否则的话,如果不使用该模型的话,如果用户自定义一个java.lang.Object类且存放在classpath中,那么系统中将会出现多个Object类,应用程序也会变得很混乱。

        如果我们自定义一个rt.jar中已有类的同名Java类,会发现JVM可以正常编译,但该类永远无法被加载运行。






欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2