深入理解JVM-类加载器双亲委派机制

你未必出类拔萃,但一定与众不同

类加载器

文章目录

  • 类加载器
    • 类与类加载器
    • 双亲委派机制
      • 三个类加载器
      • 类加载器双亲委派模型
      • 工作过程
      • 破坏双亲委派模型
        • 第一次被破坏
        • 第二次破坏
        • 第三次破坏

通过一个类的全限定名来获取描述该类的二进制字节流,将这个动作放到Java虚拟机里去实现,以便让应用程序自己决定获取所需的类,实现这个动作的代码被称为类加载器

类与类加载器

对于任意一个类,都必须由加载它的类加载器和这个类本身一起共同确立其在虚拟机的唯一性,每一个类加载器,都拥有一个独立的类名称空间。通俗易懂点就是,比较两个类是否相同只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个Class文件,但是被同一个Java虚拟机加载,只要加载他们的类加载器不同,那么这两个类就必不相等。

这里指的相等

  • Class对象的equals()方法
  • isAssignableFrom()方法
  • isInstance()方法
  • instanceof关键字做对象所属关系判定

不同类加载器对instanceof关键字运算的结果影响

public class Demo6181400 {
    public static void main(String[] args) throws Exception{
        ClassLoader classLoader = new ClassLoader() {
            @Override
            public Class<?> loadClass(String name) throws ClassNotFoundException {
                try {
                    //拿到传过来类的类名
                    String filename = name.substring(name.lastIndexOf(".") + 1) + ".class";
                    //获取文件的输入流
                    InputStream inputStream = getClass().getResourceAsStream(filename);
                    if(inputStream == null){
                        return super.loadClass(name);
                    }
                    //读取多个字节
                    byte[] bytes = new byte[inputStream.available()];
                    inputStream.read(bytes);
                    return defineClass(name, bytes , 0 , bytes.length);
                }catch (IOException e){
                    throw new ClassNotFoundException();
                }
            }
        };
        Object object = classLoader.loadClass("com.bluedot.test618.Demo6181400").newInstance();
        System.out.println(object.getClass());
        System.out.println(object instanceof com.bluedot.test618.Demo6181400);
    }
}

运行结果

class com.bluedot.test618.Demo6181400
false

代码清单构造了一个简单的类加载器,可以加载与自己再同一路径下的Class文件,使用这个类加载器去加载com.bluedot.test618.Demo6181400并且实例化这个类的对象

对象是com.bluedot.test618.Demo6181400实例化出来的,但是和com.bluedot.test618.Demo6181400做所属类型检查的时候返回 了false,这是因为Java虚拟机中同时存在了两个Demo6181400,一个是虚拟机的应用程序加载类加载的,一个是我们自定义的类加载器加载的,虽然来自同一个class文件,但是在虚拟机中却是两个互相独立的类。

双亲委派机制

三个类加载器

绝大多数Java程序都会使用以下三个系统提供的类加载器来进行加载

  • 启动类加载器Bootstrap Class Loader

    • 这个类加载器负责加载存放在\lib目录,或者被-Xbootclasspath参数所指定的路径下存放的,而且是虚拟机能够识别的类库,名字不符合的类库即使放在lib目录下也不会被加载

    • 启动类加载器无法被Java程序直接引用

    • 用户在编写自定义类加载器时,如果需要把加载请求委派给引导类加载器去处理,那么直接使用null去使用即可。

  • 扩展类加载器Extension Class Loader

    • 这个类加载器负责加载\lib\ext目录,或者被javax.ext.dirs系统变量所指定的路径中所有的类库
    • 由于扩展类加载器是由Java代码实现的,开发者可以直接在程序中使用扩展类加载器来加载Class文件
  • 应用程序加载器Application Class Loader

    • 这个类加载器负载加载用户类路径ClassPath上所有的类库
    • 由于应用程序类加载器是ClassLoader类中的getSystemClassLoader()方法的返回值
    • 如果应用程序没有自己定义自己的类加载器,一般情况下这个就是程序中默认的类加载器

类加载器双亲委派模型

深入理解JVM-类加载器双亲委派机制_第1张图片

工作过程

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

使用双亲委派类型来组织类加载器之间的关系的优点

  • Java中的类随着它的类加载器一起具备了一种带有优先级的层次关系,例如类java.lang.Object,它存放在rt.jar之中,无论哪个类加载器要加载这个类,最终都委派给处于模型最顶端的启动类加载器,因此Ocject类在程序的各种类加载环境都能够保证是同一个类

  • 保证Java程序的稳定运行极为重要,即使源码只有数十行

    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded  首先检查请求的类是不是被加载过了
            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 thrown if class not found如果父类加载器抛出ClassNotFoundException
                    // from the non-null parent class loader 说明父类无法完成加载请求
                }
    
                if (c == null) {
                    // If still not found, then invoke findClass in order 父类无法加载的时候 调用自己的findclass方法进行类加载
                    // 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;
        }
    }
    

破坏双亲委派模型

双亲委派模型并不是一个具有强制性约束的模型,而是Java推荐给开发者的类加载器实现方式,大部分的类加载器都遵循这个模型,但也有例外的情况,直到Java模块化出现为止,双亲委派模型主要出现过三次较大规模的“被破坏”的情况

第一次被破坏

双亲委派模型是在JDK1.2之后引入的,面对已经存在的用户自定义类加载器,不得不做出妥协,在java.lang.ClassLoader中添加一个新的protected方法findClass(),并且引导用户编写的类加载器尽可能去重写这个方法,而不是在loadClass方法编写代码。

第二次破坏

这次破坏是由于这个模型自身的缺陷导致的

双亲委派很好的解决了各个类加载器协作时基础类型的一致性问题,越基础的类越由上层的类加载器加载,基础类型之所以被称为基础,是因为总是作为被用户代码继承,调用的API存在,但是基础程序往往没有绝对不变的完美规则,如果有基础类型又要调回用户的代码,那该怎么办

因此引入了线程上下文类加载器,通过Thread类的setContextClassLoader方法进行设置,如果创建线程时还未设置,它将会从父线程中继承一个,如果在应用程序的全局范围内都没有设置过的话,那么这个类加载器默认就是应用程序类加载器。

第三次破坏

第三次破坏是用户对程序动态性的追求而导致的,这里的动态是指一些非常热门的名词,代码热替换,模块化热部署,说白了就是希望 Java应用程序能够像电脑外设一样,不需要关机也能让鼠标键盘升级,生产系统不需要重启就能进行热部署。

于是出现了一个OSGI。面向Java的动态模型系统,感兴趣的可以去了解一下。

你可能感兴趣的:(笔记,JVM,jvm,java)