热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

何为双亲委派原则

本篇内容主要讲解“何为双亲委派原则”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“何为双亲委

本篇内容主要讲解“何为双亲委派原则”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“何为双亲委派原则”吧!

我敢打赌大家在开发过程中经常碰到一些类加载的问题,比如:

ClassNotFoundException

Cause: java.lang.ClassNotFoundException: Cannot find class: com.cc.A

NoClassDefFoundError

Cause: java.lang.NoClassDefFoundError: Cannot find class: com.cc.A

上述问题均和java类加载有关,如果不清楚JVM中类加载的原理,上述问题会让人郁闷至极,侥幸在网上找到解决方案也只是暂时解决问题,后续在另外的场景中碰到又会继续懵逼。

我这篇文章将对 Java  类加载器的双亲委派加载原理进行阐述,并结合实例程序深究类的双亲委派加载机制,大家彻底了解掌握类加载原理,清楚了类加载原理后,碰到上述类似问题就能快速解决,并在后续开发中避免类似问题。

什么是Java类加载?

java类加载器负责将编译好的 Java class 件加载到 Java  虚拟机(JVM)中的运行时数据区中,供执行引擎调用。

java类加载在JVM体系结构中的位置如图所示:

何为双亲委派原则

jvm体系结构原图

没有类加载机制,编写的java程序就没法在JVM中运行,因此掌握java类加载是非常重要的。

JVM类加载层级关系

执行java程序时,会启动一个JVM进程,JVM在启动时会做一些初始化操作,比如获取系统参数等等,然后创建一个启动类加载器,用于加载JVM运行时必须的一些类到内存中,同时也会创建其他两个类加载器扩展类加载器和系统类加载器。

启动类加载器、扩展类加载器和系统类加载器之间的关系如下图所示:

何为双亲委派原则

jvm内置classLoader

  • **启动类加载器:**java虚拟机启动后创建的第一个类加载器,由C++语言实现,所以我们在java代码中查看其信息时,看到的均为null。

  • 扩展类加载器:由启动类加载器加载,并将扩展类加载器中的parent的值设置为null(表示指向启动类加载器),同时继承自URLClassLoader。

  • 系统类加载器:由启动类加载器加载,并将系统类加载期中的parent的值设置为上述创建的扩展类加载器。,同时继承自URLClassLoader。

在代码中可以通过如下方式查看类加载中的parent指向:

何为双亲委派原则

代码查看类加载器的parent

注意:这里的parent不是java的继承机制,而是类加载器中的一个实例属性,用于在类加载时的委托对象,parent属性定义在其所继承的ClassLoader中,定义如下所示。

public abstract class ClassLoader {    ....................     // The parent class loader for delegation     private final ClassLoader parent;

JVM类加载的默认加载路径

每种类型的类加载器默认都会有自己的加载路径,启动类加载器、扩展类加载器和系统类加载器的默认加载路径如下图所示:

何为双亲委派原则

三种类加载器的加载路径

如上图所示:

1、启动类加载器(BootClassLoader)由C++语言编写,负责在JVM启动时加载jdk自身的一些核心class类(jar包形式)到JVM中,加载时寻找资源的路径由只读系统属性:”sun.boot.class.path“  指定,一般为:”JAVA_HOME/jre/classes“目录(在该目录下只能放class文件,jar包形式文件不生效)。

查看启动类加载类加载路径可以通过获取系统属性:”sun.boot.class.path“进行查看,如图所示:

何为双亲委派原则

lancher中设置启动类加载路径

何为双亲委派原则

启动类加载器加载路径

2、扩展类加载器(ExtClassLoader),负责加载位于系统属性:"java.ext.dirs"指向的目录下加载class文件(jar包或者直接class文件形式)到JVM中,比如通常ext类加载路径为:”$JAVA_HOMEx/jre/lib/ext“  。

支持在JVM启动之前进行修改路径,运行中修改路径不生效,扩展类路径中仅支持jar包的加载。

查看扩展类加载器的类加载路径可以通过获取系统属性:”java.ext.dirs“进行查看或向上转型为URLClassLoader(上面说扩展类加载器继承自URLClassLoader),查看位于父类URLClassLoader中urls属性的方式进行查看,如图所示:

何为双亲委派原则

扩展类加载器路径

3、系统类加载器(AppClassLoader),负责加载应用classpath路径下的class文件(jar包或者直接class文件形式)到JVM中,当系统中没有设置classpath路径时,默认加载当前路径下的class文件。

查看系统类加载器的类加载路径可以通过获取系统属性:”java.class.path“进行查看或向上转型为URLClassLoader上面说扩展类加载器继承自URLClassLoader),查看位于父类URLClassLoader中urls属性的方式进行查看,如图所示:

何为双亲委派原则

系统类加载路径

JVM类加载双亲委托机制

JVM加载class类文件到虚拟机时,默认首先采用系统类加载器去加载用到的class类,采用的是双亲委托加载机制。

所谓双亲委托,顾名思义,就是当前类加载器(以系统类加载器为例)在加载一个类时,委托给其双亲(注意这里的双亲指的是类加载器中parent属性指向的类加载器)先进行加载。

双亲类加载器在加载时同样委托给自己的双亲,如此反复,直到某个类加载器没有双亲为止(通常情况下指双亲为null,也即为当前的双亲为扩展类加载器,其parent为启动类加载器),然后开始在依次在各自的类路径下寻找、加载class类。

如下图所示:

何为双亲委派原则

双亲委派

双亲委托加载实例实例

采用JDK版本

java version "1.8.0_261" Java(TM) SE Runtime Environment (build 1.8.0_261-b12) Java HotSpot(TM) 64-Bit Server VM (build 25.261-b12, mixed mode)

本实例涉及到两个类:TestMain.java 和  A.java,期中TestMain为启动类,在启动类中调用类A中的方法执行进行输出,分别输出启动类和被依赖类的类加载器信息,类定义如下所示:

何为双亲委派原则

A_java

何为双亲委派原则
TestMain

我们将两个java文件拷贝到某个目录下,在我本地比如放在E:\java_app目录下,windows下打开命令行窗口,切换到E:\java_app,对当前java文件进行编译,执行命令javac  TestMain.java。

此时会在当前目录下生产对应的class文件(这里只需要对TestMain执行编译命令,因为TestMain依赖了A,所以Jdk编译器就会自动先去编译依赖的A),如图所示:

何为双亲委派原则

编译命令

接下来我们将观察java类加载机制是怎样实现双亲委托加载的。

委托给扩展类加载器加载

由于扩展类在自身类路径下加载只支持寻找jar包的方式,因此我们通过工具将A.class文件打包进A.jar。

然后将A.jar放置到扩展类加载路径:$JAVA_HOME/jre/lib/ext,同时保留当前目录中的A.class文件。如图所示:

何为双亲委派原则

扩展委派

此时在当前目录:E:\java_app下仍然保留有A.class文件,在扩展类加载器路径下多了一个包含了A.class的A.jar文件,在当前目录下执行java命令执行TestMain,命令为:java  TestMain,输出如下所示:

何为双亲委派原则

扩展委派结果

由上图输出结果可知,class  A虽然在系统类加载器的加载路径中,但由于类加载的委托机制,A首先将由系统类加载器委托给其双亲扩展类加载器进行加载,刚好在扩展类加载器的加载路径中包含了A.class(包含在A.jar中),所以A最终由扩展类加载器进行了加载。

委托给启动类加载器进行加载

通常情况下,普通类的加载不应该委托给启动类加载器进行加载,因为前面说过启动类加载器由C++实现,在java虚拟机启动时生成的,在java环境中获取她的信息均为null。

本实例为了探究类加载的双亲委托机制,所以特意将构造一个将普通类委托给其加载的场景。

前面在讲到启动类加载器加载路径时指出了启动类加载器的加载路径由只读系统属性”sun.boot.class.path“  指定,且仅支持加载该目录下固定的jar文件。

在jdk8中还有”$JAVA_HOME/jre/classes“目录也是启动类加载器加载的路径(该路径默认可能不存在,可以手工创建一个),在该目录下只能放class文件,jar包形式文件不生效。

因此,本实例程序将当前目录下的A.class文件拷贝到启动类加载器的类路径:”$JAVA_HOME/jre/classes“中,同时保留当前目录中的A.class文件,也保留扩展类加载器类路径中的A.jar。

类存放路径如图所示:

何为双亲委派原则

委派启动

在当前目录:E:\java_app目录下执行命令运行TestMain,命令为:java TestMain,输出如下所示:

何为双亲委派原则

委派启动结果

由上图输出结果可知,class  A虽然在系统类加载器的加载路径中,也存在扩展类加载器的加载路径中,但由于类加载的委托机制,A首先将由系统类加载器委托给其双亲扩展类加载器进行加载。

扩展类加载器又会继续进行委托加载(实际上因为扩展类加载器的parent:启动类加载器为null,所以此时的委托动作实际上就是去启动类加载器的加载路径中寻找class  A),最终由启动类加载进行了A的加载。

双亲委托加载方向

类加载器在加载类时,只能向上递归委托其双亲进行类加载,而不可能从双亲再反向委派当前类加载器来进行类加载。

在中国象棋中,卒子过河之后的行走轨迹永远只能是前进或者左右平移,可以很形象的比作双亲委托类加载的这种方向性。

  • 卒子过河比喻当前类加载器委派其双亲加载了某个类。这个类的后续依赖的加载已经和当前类加载器没有关系。

  • 过河之后的卒子只能前进,表示双亲在加载类的依赖类时,只能继续递归进行双亲委派。

  • 左右平移表示双亲在递归双亲委派加载失败后,在双亲类加载器自己的加载路径中进行加载。

为了表明委派具有方向性,我们继续拿上面的TestMain.class和A.class两个类做实验。

上述委托实例中我们的场景时是:TestMain中依赖了A,我们将A通过双亲委托方式进行了加载,本次实验中,我们将TestMain委托给双亲加载。

参照上述的操作步骤,将TestMain.class打进TestMain.jar中,放到扩展类加载器的加载路径中,同时也保留TestMain.class到当前目录,如下图所示:

何为双亲委派原则

委派加载顺序1

切换到当前应用目录,执行java命令运行程序:java TestMain,执行结果如下所示:

何为双亲委派原则

委派顺序执行结果

如上图所示,出现错误了,TestMain被扩展类加载器加载了,依赖的A却没有能被加载到。

原因就是上述说的委派加载具有方向性导致的:

1、运行java命令执行TestMain程序时,系统类加载器准备加载TestMain,根据双亲委派机制,先委派给其双亲进行加载,最后,双亲扩展类加载器在其加载路径中的TestMain.jar中找到了TestMain.class,完成了TestMain的加载。

2、TestMain中依赖了A,此时,会根据加载了TestMain的类加载器:扩展类加载器去加载A,加载方式根据委托机制递归委托给双亲加载,扩展类加载器的双亲为启动类加载器,在启动类加载器的加载路径中不存在A,加载失败,此时由扩展类加载器在自己的加载路径中加载A,也因为加载路径中没有A.class存在,A.class存在于系统类加载器的加载路径中,但是扩展类加载器不会再返回去委托系统类加载器进行加载,所以直接抛出加载失败异常,出现了上述的错误。

到此,相信大家对“何为双亲委派原则”有了更深的了解,不妨来实际操作一番吧!这里是编程笔记网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!


推荐阅读
  • Android中将独立SO库封装进JAR包并实现SO库的加载与调用
    在Android开发中,将独立的SO库封装进JAR包并实现其加载与调用是一个常见的需求。本文详细介绍了如何将SO库嵌入到JAR包中,并确保在外部应用调用该JAR包时能够正确加载和使用这些SO库。通过这种方式,开发者可以更方便地管理和分发包含原生代码的库文件,提高开发效率和代码复用性。文章还探讨了常见的问题及其解决方案,帮助开发者避免在实际应用中遇到的坑。 ... [详细]
  • 您的数据库配置是否安全?DBSAT工具助您一臂之力!
    本文探讨了Oracle提供的免费工具DBSAT,该工具能够有效协助用户检测和优化数据库配置的安全性。通过全面的分析和报告,DBSAT帮助用户识别潜在的安全漏洞,并提供针对性的改进建议,确保数据库系统的稳定性和安全性。 ... [详细]
  • 浏览器作为我们日常不可或缺的软件工具,其背后的运作机制却鲜为人知。本文将深入探讨浏览器内核及其版本的演变历程,帮助读者更好地理解这一关键技术组件,揭示其内部运作的奥秘。 ... [详细]
  • 本文详细解析了Java类加载系统的父子委托机制。在Java程序中,.java源代码文件编译后会生成对应的.class字节码文件,这些字节码文件需要通过类加载器(ClassLoader)进行加载。ClassLoader采用双亲委派模型,确保类的加载过程既高效又安全,避免了类的重复加载和潜在的安全风险。该机制在Java虚拟机中扮演着至关重要的角色,确保了类加载的一致性和可靠性。 ... [详细]
  • 深入解析Java虚拟机的内存分区与管理机制
    Java虚拟机的内存分区与管理机制复杂且精细。其中,某些内存区域在虚拟机启动时即创建并持续存在,而另一些则随用户线程的生命周期动态创建和销毁。例如,每个线程都拥有一个独立的程序计数器,确保线程切换后能够准确恢复到之前的执行位置。这种设计不仅提高了多线程环境下的执行效率,还增强了系统的稳定性和可靠性。 ... [详细]
  • 深入理解Java中的多态性概念及其应用
    多态是面向对象编程中的三大核心特性之一,与封装和继承共同构成了面向对象的基础。多态使得代码更加灵活和可扩展,封装和继承则为其提供了必要的支持。本文将深入探讨多态的概念及其在Java中的具体应用,帮助读者全面理解和掌握这一关键知识点。 ... [详细]
  • 在本地环境中部署了两个不同版本的 Flink 集群,分别为 1.9.1 和 1.9.2。近期在尝试启动 1.9.1 版本的 Flink 任务时,遇到了 TaskExecutor 启动失败的问题。尽管 TaskManager 日志显示正常,但任务仍无法成功启动。经过详细分析,发现该问题是由 Kafka 版本不兼容引起的。通过调整 Kafka 客户端配置并升级相关依赖,最终成功解决了这一故障。 ... [详细]
  • Java中不同类型的常量池(字符串常量池、Class常量池和运行时常量池)的对比与关联分析
    在研究Java虚拟机的过程中,笔者发现存在多种类型的常量池,包括字符串常量池、Class常量池和运行时常量池。通过查阅CSDN、博客园等相关资料,对这些常量池的特性、用途及其相互关系进行了详细探讨。本文将深入分析这三种常量池的差异与联系,帮助读者更好地理解Java虚拟机的内部机制。 ... [详细]
  • 本文详细介绍了如何在Java Web服务器上部署音视频服务,并提供了完整的验证流程。以AnyChat为例,这是一款跨平台的音视频解决方案,广泛应用于需要实时音视频交互的项目中。通过具体的部署步骤和测试方法,确保了音视频服务的稳定性和可靠性。 ... [详细]
  • 在本文中,我们将探讨如何在Docker环境中高效地管理和利用数据库。首先,需要安装Docker Desktop以确保本地环境准备就绪。接下来,可以从Docker Hub中选择合适的数据库镜像,并通过简单的命令将其拉取到本地。此外,我们还将介绍如何配置和优化这些数据库容器,以实现最佳性能和安全性。 ... [详细]
  • 深入解析CAS机制:全面替代传统锁的底层原理与应用
    本文深入探讨了CAS(Compare-and-Swap)机制,分析了其作为传统锁的替代方案在并发控制中的优势与原理。CAS通过原子操作确保数据的一致性,避免了传统锁带来的性能瓶颈和死锁问题。文章详细解析了CAS的工作机制,并结合实际应用场景,展示了其在高并发环境下的高效性和可靠性。 ... [详细]
  • Web开发框架概览:Java与JavaScript技术及框架综述
    Web开发涉及服务器端和客户端的协同工作。在服务器端,Java是一种优秀的编程语言,适用于构建各种功能模块,如通过Servlet实现特定服务。客户端则主要依赖HTML进行内容展示,同时借助JavaScript增强交互性和动态效果。此外,现代Web开发还广泛使用各种框架和库,如Spring Boot、React和Vue.js,以提高开发效率和应用性能。 ... [详细]
  • PHP预处理常量详解:如何定义与使用常量 ... [详细]
  • 在探讨Hibernate框架的高级特性时,缓存机制和懒加载策略是提升数据操作效率的关键要素。缓存策略能够显著减少数据库访问次数,从而提高应用性能,特别是在处理频繁访问的数据时。Hibernate提供了多层次的缓存支持,包括一级缓存和二级缓存,以满足不同场景下的需求。懒加载策略则通过按需加载关联对象,进一步优化了资源利用和响应时间。本文将深入分析这些机制的实现原理及其最佳实践。 ... [详细]
  • 今天我开始学习Flutter,并在Android Studio 3.5.3中创建了一个新的Flutter项目。然而,在首次尝试运行时遇到了问题,Gradle任务 `assembleDebug` 执行失败,退出状态码为1。经过初步排查,发现可能是由于依赖项配置不当或Gradle版本不兼容导致的。为了解决这个问题,我计划检查项目的 `build.gradle` 文件,确保所有依赖项和插件版本都符合要求,并尝试更新Gradle版本。此外,还将验证环境变量配置是否正确,以确保开发环境的稳定性。 ... [详细]
author-avatar
手机用户2602918323
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有