欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

类加载器与双亲委派机制

程序员文章站 2022-07-06 15:32:39
目录一、类与类加载器二、三层类加载器三、双亲委派机制四、总结一、类与类加载器 类加载器用于实现类的加载,加载器会把载入内存中的类生成一个java.lang.Class实例对象。对于任意一个类, 都必须由加载它的类加载器和这个类本身一起共同确立其在Java虚拟机中的唯一性。也就是说:比较两个类是否“相等”, 只有在这两个类是由同一个类加载器加载的前提下才有意义, 否则, 即使这两个类来源于同一个Class文件, 被同一个Java虚拟机加载, 只要加载它们的类加载器......

目录

一、类与类加载器

二、三层类加载器

三、双亲委派机制

四、总结


 

一、类与类加载器

         类加载器用于实现类的加载,加载器会把载入内存中的类生成一个java.lang.Class实例对象。对于任意一个类, 都必须由加载它的类加载器和这个类本身一起共同确立其在Java虚拟机中的唯一性。也就是说:比较两个类是否“相等”, 只有在这两个类是由同一个类加载器加载的前提下才有意义, 否则, 即使这两个类来源于同一个Class文件, 被同一个Java虚拟机加载, 只要加载它们的类加载器不同, 那这两个类就必定不相等。下面是类加载器对instanceof关键字运算的结果的影响:

package com.me.jvm;

import java.io.IOException;
import java.io.InputStream;

public class ClassLoaderTest {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        ClassLoader loader = new ClassLoader() {
            @Override
            public Class<?> loadClass(String name) throws ClassNotFoundException {
                try {
                    String fileName = name.substring(name.lastIndexOf(".") + 1)+".class";
                    InputStream is = getClass().getResourceAsStream(fileName);
                    if (is == null) {
                        return super.loadClass(name);
                    }
                    byte[] b = new byte[is.available()];
                    is.read(b);
                    return defineClass(name, b, 0, b.length);
                } catch (IOException e) {
                    throw new ClassNotFoundException(name);
                }
            }
        };
        Object obj = loader.loadClass("com.me.jvm.ClassLoaderTest").newInstance();
        System.out.println(obj.getClass());
        System.out.println(obj instanceof  ClassLoaderTest);
        System.out.println(obj.getClass().getClassLoader());
        System.out.println(ClassLoaderTest.class.getClassLoader());
        /**
         * 打印结果:
         * class com.me.jvm.ClassLoaderTest
         * false
         * com.me.jvm.ClassLoaderTest$1@74a14482
         * sun.misc.Launcher$AppClassLoader@18b4aac2
         */
    }
}

        上面的示例显示:在自定义的classLoader去加载了一个名为“com.me.jvm.ClassLoaderTest”的类, 并实例化了这个类的对象。前两行输出结果中, 从第一行可以看到这个对象确实是类com.me.jvm.ClassLoaderTest实例化出来的, 但在第二行的输出中却发现这个对象与类com.me.jvm.ClassLoaderTest做所属类型检查的时候返回了false。 这是因为Java虚拟机中同时存在了两个ClassLoaderTest类, 一个是由虚拟机的应用程序类加载器所加载的(参考第四行结果), 另外一个是由我们自定义的类加载器加载的(参考第三行结果), 虽然它们都来自同一个Class文件, 但在Java虚拟机中仍然是两个互相独立的类。

二、三层类加载器

        本节内容将针对JDK 8及之前版本的Java来介绍什么是三层类加载器。正如我们通常认为的那样,绝大多数Java程序都会使用到以下3个系统提供的类加载器来进行加载。分别是:启动类加载器、扩展类加载器、应用程序类加载器。贴出如下代码来查看各层加载器及其父类加载器:

/*示例1:输出加载器的父类加载器*/
        Object obj = loader.loadClass("com.me.jvm.ClassLoaderTest").newInstance();//loader为第一节中自定义加载器
        //1.1
        System.out.println(obj.getClass().getClassLoader());//com.me.jvm.ClassLoaderTest$1@74a14482
        //1.2
        System.out.println(ClassLoaderTest.class.getClassLoader());//sun.misc.Launcher$AppClassLoader@18b4aac2
        //1.3
        System.out.println(ClassLoaderTest.class.getClassLoader().getParent());//sun.misc.Launcher$ExtClassLoader@14ae5a5
        //1.4
        System.out.println(ClassLoaderTest.class.getClassLoader().getParent().getParent());//null
/*示例2:输出各个类的加载器*/
        //2.1
        System.out.println(ClassLoaderTest.class.getClassLoader());//sun.misc.Launcher$AppClassLoader
        //2.2
        System.out.println(ClassLoaderTest.class.getClassLoader().getClass().getClassLoader());//null
        //2.3
        System.out.println(com.sun.nio.zipfs.ZipPath.class.getClassLoader());//sun.misc.Launcher$ExtClassLoader@14ae5a5
        //2.4
        System.out.println(com.sun.nio.zipfs.ZipPath.class.getClassLoader().getClass().getClassLoader());//null

        从示例一中可以看出,其中三层加载器及自定义加载器是相互补充依赖,并不是继承关系,查看源码的话可以看出加载器ClassLoader类里有一个final修饰的ClassLoader类型的parent属性。加载器的依赖顺序是:自定义加载器的-->应用程序类加载器(AppClassLoader)-->扩展类加载器(ExtClassLoader)-->启动类加载器(null)。从示例二中可以看出,各层加载器加载的类是不一样的。下面是三层加载器的说明 :

  • ·启动类加载器(Bootstrap Class Loader) 

        这个类加载器负责加载存放在<JAVA_HOME>\lib目录, 或者被-Xbootclasspath参数所指定的路径中存放的, 而且是Java虚拟机能够识别的(按照文件名识别, 如rt.jar、 tools.jar, 名字不符合的类库即使放在lib目录中也不会被加载) 类库加载到虚拟机的内存中。 启动类加载器无法被Java程序直接引用, 用户在编写自定义类加载器时,如果需要把加载请求委派给引导类加载器去处理, 那直接使用null代替即可( null值来代表引导类加载器的约定规则,可参考上面示例2.2或2.4)。

  • ·扩展类加载器(Extension Class Loader) :

        这个类加载器是在类sun.misc.Launcher$ExtClassLoader中以Java代码的形式实现的。 它负责加载<JAVA_HOME>\lib\ext目录中, 或者被java.ext.dirs系统变量所指定的路径中所有的类库。 根据“扩展类加载器”这个名称, 就可以推断出这是一种Java系统类库的扩展机制, JDK的开发团队允许用户将具有通用性的类库放置在ext目录里以扩展Java SE的功能, 在JDK9之后, 这种扩展机制被模块化带来的天然的扩展能力所取代。 由于扩展类加载器是由Java代码实现的, 开发者可以直接在程序中使用扩展类加载器来加载Class文件(可参考上面示例2.3)。

  • ·应用程序类加载器(Application Class Loader)/系统类加载器 :

        这个类加载器由sun.misc.Launcher$AppClassLoader来实现。 由于应用程序类加载器是ClassLoader类中的getSystemClassLoader()方法的返回值, 所以有些场合中也称它为“系统类加载器”。 它负责加载用户类路径(ClassPath) 上所有的类库, 开发者同样可以直接在代码中使用这个类加载器。 如果应用程序中没有自定义过自己的类加载器, 一般情况下这个就是程序中默认的类加载器。(可参考上面示例2.1)。

三、双亲委派机制

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

        使用双亲委派模型来组织类加载器之间的关系, 一个显而易见的好处就是Java中的类随着它的类加载器一起具备了一种带有优先级的层次关系。 例如类java.lang.Object, 它存放在rt.jar之中, 无论哪一个类加载器要加载这个类, 最终都是委派给处于模型最顶端的启动类加载器进行加载, 因此Object类在程序的各种类加载器环境中都能够保证是同一个类。 反之, 如果没有使用双亲委派模型, 都由各个类加载器自行去加载的话, 如果用户自己也编写了一个名为java.lang.Object的类, 并放在程序的ClassPath中, 那系统中就会出现多个不同的Object类, Java类型体系中最基础的行为也就无从保证, 应用程序将会变得一片混乱。双亲委派模型对于保证Java程序的稳定运作极为重要, 但它的实现却异常简单,全部集中在java.lang.ClassLoader的loadClass()方法之中, 如代码如下:

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 1、查看是否已经加载过此类
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    //如果抛出ClassNotFoundException,说明父类加载器无法加载
                }

                if (c == null) {
                    // 假如父类加载此类失败,调用自身findClass方法再进行类加载
                    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;
        }
    }

         这段代码的逻辑: 先检查请求加载的类型是否已经被加载过, 若没有则调用父加载器的loadClass()方法, 若父加载器为空则默认使用启动类加载器作为父加载器。 假如父类加载器加载失败,抛出ClassNotFoundException异常的话, 才调用自己的findClass()方法尝试进行加载。

        下面是类加载器加载流程图:

类加载器与双亲委派机制

四、总结

本文所用jdk版本为java1.8.0_201,以下是总结:

  • 自定义加载器可以通过重写ClassLoader的loadClass方法来实现。
  • class都是通过classloader来装载的。
  • 只有当你使用该class的时候才会去装载。
  • 同一个加载器只会装载相同的class一次;同一个Java虚拟机加载, 只要加载它们的类加载器不同, 那这两个类就必定不相等。
  • 双亲委派机制保证java运行安全稳定。

参考:《深入理解Java虚拟机:JVM高级特性与最佳实践》

 

本文地址:https://blog.csdn.net/changlina_1989/article/details/112483881