[ Java ] 一文说透所谓的双亲委派

 

   所谓的双亲委派我其实觉得更应该翻译成 “父辈代理” , 因为这个双亲在原意中就是父/母辈这一代,并且代码体现的意思也是把类交由父类加载。那么在了解双亲委派的基础上我们还要知道 【类的加载过程】 分为三个阶段即   “装载——>连接——>初始化” ,而我们所说的双亲委派就是发生在 【装载】 阶段中。
 
 
 
   PS: 类似于这样提升理解成本的翻译偏差在计算机领域还是有一小部分的,比如套接字-socket的本意是插座、插孔,想象服务器就像一个大插排,包含很多插座,客户端就是像一个插头,每一个线程代表一条电线,客户端将电线的插头插到服务器插排上对应的插座上,就可以开始通信了。另外路由,本意其实就是寻找的意思或者寻址更恰当,正则表达式根据本意可以理解成符合某种常规规则的表达式....
 
 
 


 

正确的理解&翻译(委派模型 或 父委派模型)

 

oracle 官方文档关于 jvm 类加载机制所用的描述是:
 
   The Java platform uses a delegation model for loading classes. The basic idea is that every class loader has a “parent” class loader. When loading a class, a class loader first “delegates” the search for the class to its parent class loader before attempting to find the class itself.

 

翻译过来就是:
 
   java 平台使用 委派模型来加载类。 基本思想就是, 每一个类加载器都有一个父加载器, 当需要加载一个 class时, 首先把该 class 的查询和加载优先委派给父加载器进行, 如果父加载器无法加载该 class, 再去尝试自行加载这个 class 。

 
 
 

在了解具体原理之前还需要在大致了解下 加载类的三大阶段JVM预定义的三种类型类加载器:

 
 

三大阶段分为:装载——>连接——>初始化

1.装载 (Loading
 
该阶段负责找到待加载类的二进制 class 文件, 并把它以 bytecode(字节码) 的形式装载到虚拟机。 在这个过程中, JVM 会给这个类分配一个基本的内存结构, 但是方法, 变量域, 和它引用到的其他类在这个阶段都还没有处理, 也就是说, 这个类在目前阶段还不可用

 
 
2.链接 (Linking)
 
这个步骤又可细分为3个阶段

  • 字节码验证
    验证字节码是否是一个正确,符合规范的类字节码

  • 类准备
    为这个类定义好必须的数据结构以表示成员变量域, 方法, 以及实现的接口等等

  • 解析

    把这个类锁引用的其他类全部加载进来 , 引用的方式有如下几种:

    • 继承
    • 实现接口
    • 域变量
    • 方法定义
    • 方法中定义的本地变量

 
 
3.初始化(Initializing)
 
执行类中定义的静态代码块, 初始化静态变量为默认值

 
 

三大加载器分为: BootstrapClassLoader-启动类加载器, ExtClassLoader-扩展类加载器, AppClassLoader-应用类加载器。

  • 启动类(Bootstrap)加载器:c++编写,加载java核心库 java.*,构造ExtClassLoaderAppClassLoader。由于此类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作

  • 扩展类(Extension)加载器:java编写,加载扩展库,如classpath中的jrejavax.*或者
    java.ext.dir 指定位置中的类,开发者可以直接使用标准扩展类加载器。

  • 应用类[也叫系统类](Application)加载器:java编写,加载程序所在的目录,如user.dir所在的位置的class

 
 
   PS: 在Java的日常应用程序开发中,类的加载几乎是由上述3种类加载器相互配合执行的,在必要时,我们还可以自定义类加载器,需要注意的是,Java虚拟机对class文件采用的是按需加载的方式,也就是说当需要使用该类时才会将它的class文件加载到内存生成class对象,而且加载某个类的class文件时,Java虚拟机采用的是双亲委派模式即把请求交由父加载器处理

 
 

那么在了解完它的大致原理后我们来看看java.lang.ClassLoader.java( jdk1.8.0_101)的源码:



public Class<?> loadClass(String name) throws ClassNotFoundException {
     return loadClass(name, false);
}

  
protected Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException
        {
            synchronized (getClassLoadingLock(name)) {
                // 笔者注:首选会在这里判断类是否已经被加载,若被加载直接返回
                // First, check if the class has already been loadCed
                Class<?> c = findLoadedClass(name);
                if (c == null) {
                    long t0 = System.nanoTime();
                    try {
                        // 笔者注:在此递归交由父加载器加载,若父加载器不存在证明到顶层了,就执行BootstrapLoad加载
                        if (parent != null) {
                            //笔者注:递归交由父加载器
                            c = parent.loadClass(name, false);
                        } else {
                             // 笔者注:顶层父辈加载器BootstrapLoad加载
                            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();
                        // 笔者注:若加载完后还是为空则依次向下递归交由交由实现了ClassLoader的子加载器加载
                        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;
            }
        }




 
 
 

双亲委派模型工作工程:

1.当Application ClassLoader 收到一个类加载请求时,他首先不会自己去尝试加载这个类,而是将这个请求委派给父类加载器Extension ClassLoader去完成。

 
2.当Extension ClassLoader收到一个类加载请求时,他首先也不会自己去尝试加载这个类,而是将请求委派给父类加载器Bootstrap
ClassLoader去完成。

 
3.如果Bootstrap ClassLoader加载失败(在<JAVA_HOME>\lib中未找到所需类),就会让Extension ClassLoader尝试加载。

 
4.如果Extension ClassLoader也加载失败,就会使用Application ClassLoader加载。

 
5.如果Application ClassLoader也加载失败,就会使用自定义加载器去尝试加载。
 
 
 
                    在这里插入图片描述

 
 
 
在这里插入图片描述
 
 
 

结束语,知道这些有什么用?

1. 面试
 
2. 研究Tomcat、JBoss等Servlet容器原理
 
3. 如果你不想自己的代码被反编译,可以将编译后的代码加密,用自己的类加载器解密

 

这里简要总结第一点,二三点则需要另外学习了:

双亲委派的两个关键作用:

1、防止重复加载同一个.class。上面代码可知类加载前会先查询一遍是否已被加载,若加载过,就不会再加载一遍。保证文件安全的同时也略微提升了效率(因为省去后续装载步骤)。
 
2、保证核心.class不能被篡改。通过向上委托方式,不会去篡改核心.class,即使篡改也不会去加载,即使加载也不会是同一个.class对象了。
 
    PS:假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。 可能你会想,如果我们在classpath路径下自定义一个名为java.lang.SingleInterge类(该类是胡编的)呢?

    该类并不存在java.lang中,经过双亲委托模式,传递到启动类加载器中,由于父类加载器路径下并没有该类,所以不会加载,将反向委托给子类加载器加载,最终会通过系统类加载器加载该类。但是这样做是不允许,因为java.lang是核心API包,需要访问权限,强制加载将会抛出 java.lang.SecurityException 异常,所以无论如何都无法加载成功的。
 

 
 
 

☑️ [参考博文] —— 深入理解Java类加载器(ClassLoader))

☑️ [参考博文] —— 面试题之窒息翻译:类加载机制的双亲委派(正解:父委派模型)

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

削尖的螺丝刀

我就随便打开看看,非常佛系..

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值