• 双亲委派机制的作用



    类加载过程

    先简单说一下java的类加载器

    类加载器,顾名思义就是一个可以将Java字节码加载为java.lang.Class实例的工具。这个过程包括,读取字节数组、验证、解析、初始化等。另外,它也可以加载资源,包括图像文件和配置文件。

    类从被加载到内存中开始,到卸载出内存为止。它的生命周期总共七个阶段:加载---->验证---->准备---->解析---->初始化---->使用---->卸载。

    1.加载(Loading):根据类的全限定名(包括包路径和类名),定位并读取类文件的字节码。

    2.链接(Linking):将类的字节码转换为可以在虚拟机中运行的格式。链接过程包括三个阶段:

    (1)验证(Verification):验证字节码的正确性和安全性,确保它符合Java虚拟机的规范。

    (2)准备(Preparation):为类的静态变量分配内存,并设置默认的初始值。
    (3)解析(Resolution):将类的符号引用(比如方法和字段的引用)解析为直接引用(内存地址)。

    3.初始化(Initialization):执行类的初始化代码,包括静态变量的赋值和静态块的执行。

    而在java中类是按需加载,(一个类只会被加载一次)

    1、new创建对象

    2、子类加载触发父类加载

    3、调用静态成员

    4、通过反射动态加载


    一、java有哪些类加载器?

    类加载器是Java虚拟机用于加载类文件的一种机制。在Java中,每个类都由类加载器加载,并在运行时被创建为一个Class对象。类加载器负责从文件系统、网络或其他来源中加载类的字节码,并将其转换为可执行的Java对象。类加载器还负责解析类的依赖关系,即加载所需的其他类。

    Java虚拟机定义了三个主要的类加载器:

    启动类加载器(Bootstrap Class Loader):也称为根类加载器,它负责加载Java虚拟机的核心类库,如java.lang.Object等。启动类加载器是虚拟机实现的一部分,它通常是由本地代码实现的,不是Java类。

    扩展类加载器(Extension Class Loader):它是用来加载Java扩展类库的类加载器。扩展类库包括javax和java.util等包,它们位于jre/lib/ext目录下。

    应用程序类加载器(Application Class Loader):也称为系统类加载器,它负责加载应用程序的类。它会搜索应用程序的类路径(包括用户定义的类路径和系统类路径),并加载类文件。

    除了这三个主要的类加载器,Java还支持自定义类加载器,开发人员可以根据需要实现自己的类加载器。

    如下是一个案例,代码中中parents和classLoader1 它的类加载器都是null,这是因为他们的类加载器都是Bootstrap Class Loader,而Bootstrap Class Loader是用C写的,在java无法识别。显示为null。

    public class a {
        public static void main(String[] args) {
    
            // AppClassLoader@63947c6b
            ClassLoader classLoader = a.class.getClassLoader();
            System.out.println("类a的classLoader:"+classLoader);
    
            // PlatformClassLoader@5f205aa
            ClassLoader parent = classLoader.getParent();
            System.out.println("类a的父类classLoader:"+parent);
    
            // null
            ClassLoader parents = parent.getParent();
            System.out.println("类a的父类classLoader:"+parents);
            
            // null
            ClassLoader classLoader1 = String.class.getClassLoader();
            System.out.println("String字符串的classLoader:"+classLoader1);
    
        }
    }
    
    类a的classLoader:jdk.internal.loader.ClassLoaders$AppClassLoader@63947c6b
    类a的父类classLoader:jdk.internal.loader.ClassLoaders$PlatformClassLoader@5f205aa
    类a的父类classLoader:null
    String字符串的classLoader:null
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    二、双亲委派机制

    自定义String类

    我自己定义了一个String的类,如果我创建了这个类。那么肯定是会打印123的。(该类在我的java.lang目录下)

    package java.lang;
    
    /**
     * Description:
     *
     */
    public class String {
    
        public String() {
            System.out.println("123");
        }
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    然后我又创建了一个A类,在这个main方法中new了一个String对象。

    public class a {
         public static void main(String[] args) {
           java.lang.String a=new java.lang.String();
            System.out.println(a);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    按我们正常逻辑是不是会打印123,并且创建这个String对象。 但是结果并没有进行,创建的还是我们jdk中java.lang包下的String对象,而不是我们定义的String对象。 为什么会这样,这就涉及到我们的jvm的双亲委派机制了。

    这是"java.lang"包下的ClassLoader类的底层源码。然后将代码翻到loaderClass()方法处:

     public Class<?> loadClass(String name) throws ClassNotFoundException {
            return loadClass(name, false);
        }
        //              -----??-----
        protected Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException
        {
                // 首先,检查是否已经被类加载器加载过
                Class<?> c = findLoadedClass(name);
                if (c == null) {
                    try {
                        // 存在父加载器,递归的交由父加载器
                        if (parent != null) {
                            c = parent.loadClass(name, false);
                        } else {
                            // 直到最上面的Bootstrap类加载器
                            c = findBootstrapClassOrNull(name);
                        }
                    } catch (ClassNotFoundException e) {
                        // ClassNotFoundException thrown if class not found
                        // from the non-null parent class loader
                    }
     
                    if (c == null) {
                        // 如果仍然没有找到,则依次调用findClass方法
                        // to find the class.
                        c = findClass(name);
                    }
                }
                return c;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    这段代码是这样的

    当我们启动类加载器加载String对象时,它首先会去加载当前加载器的父类去加载,一直到Bootstrap Class Loader启动类加载器。

    启动类加载器(Bootstrap Class Loader):负责加载包名为java、javax、sun等开头的类。

    扩展类加载器(Extension Class Loader):负责加载位于jre/lib/ext目录下的类。

    应用程序类加载器(Application Class Loader):一般类都由它来加载

    头像

    这种设计有个好处是,如果有人想替换系统级别的类:String.java。篡改它的实现,在这种机制下这些系统的类已经被BootstrapClassLoader加载过了(为什么?因为当一个类需要加载的时候,最先去尝试加载的就是BootstrapClassLoader),所以其它类加载器并没有机会再去加载,从一定程度上防止了危险代码的植入。


    总结

    双亲委派的作用:

    1.防止重复加载同一个.class:通过委托去向上面问一问,加载过了,就不用再加载一遍。保证数据安全。

    2.保证核心的.class不能被篡改:通过委托方式,不会去篡改核心.clsas,即使篡改也不会去加载,即使加载也不会是同一个.class对象了。不同的加载器加载同一个.class也不是同一个Class对象。这样保证了Class执行安全。

  • 相关阅读:
    图解Intel SM4-AES-NI实现方案
    第十九章总结:Java绘图
    Unity 采用自定义通道ShaderGraph实现FullScreen的窗户雨滴效果
    Leetcode1704:判断字符串的两半是否相似
    三面阿里,被Java面试官虐哭!现场还原真实的“被虐”场景
    在SOLIDWORKS Electrical中,如何创建电气原理图符号库?
    SpringBoot学习笔记(2)——B站动力节点
    QRegExp(正则表达式)
    Flask结合gunicorn和nginx反向代理的生产环境部署及踩坑记录
    Redis主从模式下过期数据和数据不一致
  • 原文地址:https://blog.csdn.net/qq_48157004/article/details/132674506