Java-双亲委派原则

  • 前言
  • 什么是Java类加载?
  • JVM类加载层级关系
  • JVM类加载的默认加载路径
  • JVM类加载双亲委托机制
  • 双亲委托加载实例
    • 委托给扩展类加载器加载
    • 委托给启动类加载器进行加载
    • 双亲委托加载方向

前言

在开发过程中经常碰到一些类加载的问题,比如:
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体系结构中的位置

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

JVM类加载层级关系

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

启动类加载器、扩展类加载器和系统类加载器之间的关系

  • 启动类加载器:java虚拟机启动后创建的第一个类加载器,由C++语言实现,所以我们在java代码中查看其信息时,看到的均为null。
  • 扩展类加载器:由启动类加载器加载,并将扩展类加载器中的parent的值设置为null表示指向启动类加载器),同时继承自URLClassLoader。
  • 系统类加载器:由启动类加载器加载,并将系统类加载期中的parent的值设置为上述创建的扩展类加载器。,同时继承自URLClassLoader。

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

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

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

JVM类加载的默认加载路径

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

如上图所示:
1、启动类加载器(BootClassLoader)由C++语言编写,负责在JVM启动时加载jdk自身的一些核心class类(jar包形式)到JVM中,加载时寻找资源的路径由只读系统属性:”sun.boot.class.path"指定,一般为:“JAVA_HOME/jre/lib/”,在jdk8中还有
”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存在于系统类加载器的加载路径中,但是扩展类加载器不会再返回去委托系统类加载器进行加载,所以直接抛出加载失败异常,出现了上述的错误。

Java-双亲委派原则相关推荐

  1. java 委托原则_为什么说 Java SPI 的设计违反双亲委派原则

    一.双亲委派模型 1.类加载器可以细分为如下三类 启动类加载器(Bootstrap ClassLoader),负责将所有存放在\lib目录中的,或者被-Xbootclasspath参数所指定路径中,并 ...

  2. Java类的加载过程,类加载器,双亲委派原则

    Java一个类的加载过程: 1.加载 a.jvm会根据类名找到对应的类文件 b.进行文件内容读取 2.链接 链接主要是验证类中数据是否合法,然后把刚加载进来的类和其他类的关系建立清楚,主要有以下几个步 ...

  3. Java双亲委派机制

    1.什么是双亲委派? 虚拟机在加载类的过程中需要使用类加载器进行加载,而在Java中,类加载器有很多,那么当JVM想要加载一个.class文件的时候,到底应该由哪个类加载器加载呢? 这就不得不提到&q ...

  4. Java双亲委派模型是什么、优势在哪、双亲委派模型的破坏

    定义 双亲委派模式的工作原理的是;如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终 ...

  5. Java双亲委派模型:为什么要双亲委派?如何打破它?破在哪里?

    文章目录 一.前言 二.类加载器 三.双亲委派机制 1.什么是双亲委派 2.为什么要双亲委派? 四.破坏双亲委派 1.直接自定义类加载器加载 2.跳过AppClassLoader和ExtClassLo ...

  6. Java 双亲委派模型机制

    前言 Java是运行在Java的虚拟机(JVM)中的,在初步学习Java时,我们都知道,编写的Java源代码会被编译器编译成.class的字节码文件.然后ClassLoader负责将这些class文件 ...

  7. 简单的Java双亲委派机制

    双亲委派机制 Java虚拟机对class文件采用的是按需加载的方式,也就是说当需要使用该类时才会将它的class文件加载到内存生成class对象,而且加载某个类的class文件时,Java虚拟机采用的 ...

  8. 什么是java双亲委派机制

    什么是双亲委派机制 在介绍双亲委派模型之前先说下类加载器.对于任意一个类,都需要由加载它的类加载器和这个类本身统一确立在 JVM 中的唯一性,每一个类加载器,都有一个独立的类名称空间.类加载器就是根据 ...

  9. java 委托_面试官:java双亲委派机制及作用

    什么是双亲委派机制 当某个类加载器需要加载某个.class文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类. 类加载器的类别 Bootst ...

  10. 「 JVM基础 」Java双亲委派机制

    Java的双亲委派机制 参考&鸣谢 Dream_ling. weixin_39610188. JVM底层原理解析 文章目录 Java的双亲委派机制 一.介绍 二.什么是双亲委派机制 三.双亲委 ...

最新文章

  1. 探寻教育信息化着力点,创新四川省教育厅IT管理
  2. wxWidgets:拖放概述
  3. Visual Basic、C# 和 C++ 的数据类型比较(转)
  4. 春晚“宕机”魔咒失效 火山引擎助抖音成功闯关
  5. .net安装_如何安装GWAS分析软件R包:GAPIT
  6. Python+Selenium+Edge浏览器安装与简单运行(1/2)
  7. OPC 学习交流感想
  8. c#对接科大讯飞平台--语音转写
  9. java在线编辑word_java web实现在线编辑word,并将word导出(一)
  10. 解决:关于 “VMware Workstation 不可恢复错误- (vcpu-0)”
  11. Panel面板和三种布局管理器
  12. 交大网院计算机第五次作业答案,2015交大网院计算机第三次作业word操作题
  13. 如何定义智慧与关于生活的美好 - 与子同 Yue 001
  14. 互联网运营必须掌握的专业术语
  15. 如何知道自己计算机的IP,怎么知道自己电脑的ip地址,手把手教你查询电脑ip地址...
  16. kettle An error occurred, processing will be stopped: 错误 解决方法
  17. Object类型转换为Integer类型
  18. 【概率论与数理统计 第三版 浙江大学 盛骤 谢式千 潘乘毅 编】作业答案
  19. centos 安装erlang
  20. 播放器的书签--推荐使用Potplayer

热门文章

  1. C++全局变量在本文件内部和外部.cpp文件中的定义与使用
  2. 创业公司融资PPT模板
  3. unity实现多个屏幕和扩展屏幕
  4. 关于代码家(干货集中营)共享知识点汇总系列——瞎推荐
  5. Python遥感图像处理应用篇(九):使用NDVI指数数据批量计算植被覆盖度FVC
  6. 阿里需要什么样的人才?
  7. 爬虫--验证码,代理IP池
  8. 《中国人工智能学会通讯》——1.28 智能助手背后的技术
  9. 学Java的第一步--感jio不太行
  10. 5G是什么?一则视频听听专家怎么通俗讲解