厉害,京东Java开发工程师花了两个小时把JVM双亲委派机制讲清楚了

147 篇文章 0 订阅
140 篇文章 0 订阅

一、双亲委派模型

我们知道类加载机制是将一个类从字节码文件转化为虚拟机可以直接使用类的过程,但是是谁来执行这个过程中的加载过程,它又是如何完成或者说保障了类加载的准确性和安全性呢?答案就是类加载器以及双亲委派机制。

双亲委派模型的工作机制是:当类加载器接收到类加载的请求时,它不会自己去尝试加载这个类,而是把这个请求委派给父加载器去完成,只有当父加载器反馈自己无法完成这个加载请求时,子类加载器才会尝试自己去加载。

我们可以从JDK源码中将它的工作机制一窥究竟。

ClassLoader#loadClass(String,boolean)

这是在JDK1.8的java.lang.ClassLoader类中的源码,这个方法就是用于加载指定的类。

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

通过以上代码得出结论:

  • 当类加载器接收到类加载的请求时,首先检查该类是否已经被当前类加载器加载;
  • 若该类未被加载过,当前类加载器会将加载请求委托给父类加载器去完成;
  • 若当前类加载器的父类加载器为null,会委托启动类加载器完成加载;
  • 若父类加载器无法完成类的加载,当前类加载器才会去尝试加载该类。

类加载器分类

启动类加载器 Bootstrap ClassLoader

启动类加载器作为所有类加载器的鼻祖,是由C++实现的,不继承java.lang.ClassLoader类。它在虚拟机启动时会由虚拟机的一段C++代码进行加载,所以它没有父类加载器,在加载完成后,它会负责去加载扩展类加载器和应用类加载器。

启动类加载器用于加载java的核心类,位于JAVA_HOME\lib中,或者被 -Xbootclasspath参数所指定的路径中,并且是虚拟机能够识别的类库。

扩展类加载器 Extension ClassLoader

拓展类加载器继承于java.lang.ClassLoader类,它的父类加载器是启动类加载器,而启动类加载器在java中的显示就是null。

引⾃ jdk1.8 ClassLoader#getParent() ⽅法的注释,这个⽅法是⽤于获取类加载器的⽗类加载器: Returns the parent class loader for delegation. Some implementations may use null to represent the bootstrap class loader. This method will return null in such implementations if this class loader’s parent is the bootstrap class loader

扩展类加载器负责加载JAVA_HOME\lib\ext目录中的,或通过java.ext.dirs系统变量指定路径中的类库。

注意:扩展类加载器仅支持加载被打包为 .jar 格式的字节码文件。

应用类/系统类加载器 Application ClassLoader

JVM 通过双亲委派模型进行类的加载,当然我们也可以通过继承 java.lang.ClassLoader 实现自定义的类加载器。

自定义类加载器 Custom ClassLoader

自定义类加载器继承于 java.lang.ClassLoader 类,它的父类加载器是应用类加载器。

这是用户自定义的类加载器,可加载指定路径的字节码文件。

自定义类加载器需要继承 java.lang.ClassLoader 类并重写 findClass方法,用于实现自定义的加载类逻辑。

双亲委派模型的好处

基于双亲委派模型规定的这种带有优先级的层次性关系,虚拟机运行程序时就能够避免类的重复加载。
当父类类加载器已经加载过类时,如果再有该类的加载请求传递到子类加载器,子类加载器执行loadClass方法,然后委托给父类加载器尝试加载该类,但是父类加载器执行 Class<?> c = findLoadedClass(name);检查该类是否已经被加载过这一阶段就会检查到该类已经被加载过,直接返回该类,而不会再次加载此类。

双亲委派模型能够避免核心类篡改。一般我们描述的核心类是 rt.jar、tools.jar 这些由启动类加载器加载的类,这些类库在日常开发中被广泛运用,如果被篡改,后果将不堪设想。

双亲委派模型的不足

由于历史原因( ClassLoader 类在 JDK1.0 时就已经存在,⽽双亲委派模型是在 JDK1.2 之后才引⼊的),在未引⼊双亲委派模型时,⽤户⾃定义的类加载器需要继承 java.lang.ClassLoader 类并重写 loadClass() ⽅法,因为虚拟机在加载类时会调⽤ ClassLoader#loadClassInternal(String) ,⽽这个⽅法(源码如下)会调⽤⾃定义类加载重写的 loadClass() ⽅法。⽽在引⼊双亲委派模型后,ClassLoader#loadClass ⽅法实际就是双亲委派模型的实现,如果重写了此⽅法,相当于打破了双亲委派模型。为了让⽤户⾃定义的类加载器也遵从双亲委派模型, JDK新增了 findClass ⽅法,⽤于实现⾃定义的类加载逻辑。

private Class<?> loadClassInternal(String name) throws ClassNotFoundException{
	 // For backward compatibility, explicitly lock on 'this' when
	 // the current class loader is not parallel capable.
	 if (parallelLockMap == null) {
		 synchronized (this) {
		 	return loadClass(name);
		 }
	 } else {
	 	return loadClass(name);
	 }
 }

由于双亲委派模型规定的层次性关系,导致⼦类类加载器加载的类能访问⽗类类加载器加载的类,⽽⽗类类加载器加载的类⽆法访问⼦类类加载器加载的类。为了让上层类加载器加载的类能够访问下层类加载器加载的类,或者说让⽗类类加载器委托⼦类类加载器完成加载请求,JDK 引⼊了线程上下⽂类加载器,藉由它来打破双亲委派模型的屏障。

当⽤户需要程序的动态性,⽐如代码热替换、模块热部署等时,双亲委派模型就不再适⽤,类加载器会发展为更为复杂的⽹状结构。

总结

  • 7
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
JVM中的双亲委派机制是一种类加载机制,它规定了在Java中一个类被加载时如何进行类加载器的选择。根据这个机制,当一个类需要被加载时,首先会由类加载器ClassLoader检查是否已经加载过该类,如果是,则直接返回已经加载过的类;如果不是,则将该请求委派给父类加载器去加载。这样的过程会一直向上委派,直到达到顶层的引导类加载器(Bootstrap ClassLoader)。引用 引用中提到,并不是所有的类加载器都采用双亲委派机制Java虚拟机规范并没有强制要求使用双亲委派机制,只是建议使用。实际上,一些类加载器可能会采用不同的加载顺序,例如Tomcat服务器类加载器就是采用代理模式,首先尝试自己去加载某个类,如果找不到再代理给父类加载器。 引用中提到,引导类加载器(Bootstrap ClassLoader)是最早开始工作的类加载器,负责加载JVM的核心类库,例如java.lang.*包中的类。这些类在JVM启动时就已经被加载到内存中。 综上所述,JVM双亲委派机制是一种类加载机制,它通过类加载器的委派方式来加载类,首先检查是否已经加载过该类,如果没有则委派给父类加载器去加载,直到达到顶层的引导类加载器。不过,并不是所有的类加载器都采用该机制,一些类加载器可能会采用不同的加载顺序。引用<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [JVM-双亲委派机制](https://blog.csdn.net/m0_51608444/article/details/125835862)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [jvm-双亲委派机制](https://blog.csdn.net/y08144013/article/details/130724858)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值