类加载器、双亲委派机制

1 JVM是什么

Java Virtual Machine(Java虚拟机)是java程序实现跨平台的⼀个重要的⼯具(部件)

HotSpot VM,相信所有Java程序员都知道,它是Sun JDK和OpenJDK中所带的虚拟机,也是

⽬前使⽤范围最⼴的Java虚拟机。

只要装有JVM的平台,都可以运⾏java程序。那么Java程序在JVM上是怎么被运⾏的?

通过介绍以下JVM的三个组成部分,就可以了解到JVM内部的⼯作机制

  • 类加载系统:负责完成类的加载

  • 运⾏时数据区:在运⾏Java程序的时候会产⽣的各种数据会保存在运⾏时数据区

  • 执⾏引擎:执⾏具体的指令(代码)

2 类加载系统

类加载器是一个负责加载类的对象。ClassLoader 是一个抽象类。给定类的二进制名称,类加载器应尝试定位或生成构成类定义的数据。典型的策略是将名称转换为文件名,然后从文件系统中读取该名称的“类文件“。每个 Java 类都有一个引用指向加载它的 ClassLoader。不过,数组类不是通过 ClassLoader 创建的,而-是 JVM 在需要的时候自动创建的,数组类通过getClassLoader()方法获取 ClassLoader 的时候和该数组的元素类型的 ClassLoader 是一致的。

简单来说,类加载器的主要作用就是加载 Java 类的字节码( .class 文件)到 JVM 中(在内存中生成一个代表该类的 Class 对象

2.1 类的加载过程

⼀个类被加载进JVM中要经历哪⼏个过程

  • 加载: 通过io流的⽅式把字节码⽂件读⼊到jvm中(⽅法区)

  • 校验:通过校验字节码⽂件的头8位的16进制是否是java魔数cafebabe

  • 准备:为类中的静态部分开辟空间并赋初始化值

  • 解析:将符号引⽤转换成直接引⽤。——静态链接

  • 初始化:为类中的静态部分赋指定值并执⾏静态代码块。

类被加载后,类中的类型信息、⽅法信息、属性信息、运⾏时常量池、类加载器的引⽤等信息会被加载到元空间中。

2.2 类加载器

类是谁来负载加载的?——类加载器。不同级别的类由不同的加载器加载

  • Bootstrap ClassLoader 启动类加载器:负载加载jre/lib下的核⼼类库中的类,⽐如rt.jar、charsets.jar

    我们常用内置库 java.xxx.* 都在里面,比如java.util.、java.io.、java.nio.、java.lang.、java.sql.、java.math.

  • ExtClassLoader 扩展类加载器:负载加载jre/lib下的ext⽬录内的类

    ext 加载路径:System.getProperty("java.ext.dirs");
    
  • AppClassLoader 应⽤类加载器:负载加载⽤户⾃⼰写的类,加载当前应用 classpath 下的所有 jar 包和类。

    app 加载路径:System.getProperty("java.class.path");
    
  • ⾃定义类加载器:⾃⼰定义的类加载器,可以打破双亲委派机制。

在这里插入图片描述

由图可知,上一层加载器都会以父类的形式传入下一次加载。

3 双亲委派机制

3.1 双亲委派机制介绍

​ 当类加载进⾏加载类的时候,类的加载需要向上委托给上⼀级的类加载器,上⼀级继续向上委托,直到启动类加载器。启动类加载器去核⼼类库中找,如果没有该类则向下委派,由下⼀级扩展类加载器去扩展类库中,如果也没有继续向下委派,直到找不到为⽌,则报类找不到的异常。

双亲委派机制的流程如下:

类加载的核心代码如下:

ClassLoader.class中的

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
                    // 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;
        }
    }

3.2 双亲委派机制的优缺点

优点:

  • 避免重复加载:通过委派给父类加载器,可以避免同一个类被多次加载,提高了加载效率。

  • 安全性:通过双亲委派机制,核心类库由根加载器加载,可以确保核心类库的安全性,防止恶意代码替换核心类。

  • 扩展性:开发人员可以自定义类加载器,实现特定的加载策略,从而扩展Java的类加载机制。

缺点:

  • 灵活性受限:双亲委派机制对于某些特殊的类加载需求可能过于严格,限制了加载器的灵活性。

  • 破坏隔离性:如果自定义类加载器不遵循双亲委派机制,可能会破坏类加载的隔离性,导致类冲突或安全性问题。

  • 不适合动态更新:由于类加载器在加载类时会先检查父加载器是否已加载,因此在动态更新类时可能会出现问题,需要额外的处理。

总体而言,双亲委派机制通过层次结构和委派机制提供了一种有序、安全的类加载方式,但也存在一些限制和不适用的情况。

3.3 自定义类加载器实现双亲委派机制

有上面的源码可以看到,loadClass方法中会先看父类加载器中能不能调用,都不能在走findClass()方法,调用自己的加载器。可见 自定义类加载器主要就是重写findClass方法,再此类中,loadClass方法没有被重写,就会执行父类ClassLoader中的loadClass方法,只要执行的是ClassLoader中的方法,就是实现的双亲委派。

猜你喜欢

转载自blog.csdn.net/qq_43331014/article/details/133720739