双亲委派模型的破坏

article/2025/9/19 9:22:01

一、类加载机制

Java虚拟机把描述类的数据从Class文件加载进内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。

虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这动作的代码模块成为“类加载器”。

类加载器虽然只用于实现类的加载动作,但它在Java程序中起到的作用却远远不限于类加载阶段。对于任意一个类,都需要由加载他的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类命名空间。简单的说就是:比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义。

JVM的类加载机制有:

  • 全盘负责:当一个类加载器加载某个Class时,该Class所依赖和引用的其它的Class也由该类加载器负责载入,除非显示的使用另一个类加载器来载入;
  • 双亲委派模型
  • 按需加载:类的加载是按需进行的,只有使用了才会被加载;
  • 缓存机制:所有被加载过的Class都会被缓存,当要使用某个Class时,会先去缓存查找,如果缓存中没有才会读取class文件进行加载。

二、双亲委派模型

1、类加载器的分类

从虚拟机的角度来说,只存在两种不同类加载器:

  • 启动类加载器(Bootstrap ClassLoader):使用C++语言实现(只限HotSpot),是虚拟机自身的一部分;
  • 所有其他的类加载器:由Java语言实现,独立于虚拟机外部,并且全都继承自抽象类java.lang.ClassLoader。

从开发人员的角度来看,类加载还可以划分的更细致一些:

  • 启动类加载器(Bootstrap ClassLoader):负责将存放在 JAVA_HOME/lib 目录中的,或者被 -Xbootclasspath 参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如 rt.jar,名字不符合的类库即使放在 lib 目录下也不会加载)。启动类加载器无法被java程序直接引用,如果需要把加载请求委派给启动类加载器去处理,可以直接使用 null 替代;
  • 扩展类加载器(Extension ClassLoader):由 sun.misc.Launcher$ExtClassLoader 实现,它负责加载 JAVA_HOME/lib/ext 目录下的,或者被 java.ext.dirs 系统变量所指定的路径中的所有类库。开发者可以直接使用扩展类加载器;
  • 应用程序类加载器(Application ClassLoader):由 sun.misc.Launcher$AppClassLoader 实现。由于这个类加载器是 ClassLoader 中的 getSystemClassLoader() 方法的返回值,所以也叫做系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库。开发者可以直接使用这个类加载器,如果应用中没有定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

2、双亲委派工作流程

在这里插入图片描述

上图中各个类加载器之间的关系称为类加载器的双亲委派模型。双亲委派模型要求除了顶层的启动类加载器之外,其余的类加载器都应当由自己的父类加载器加载(类加载器之间的父子关系一般都使用组合关系来复用父加载器的代码)。

双亲委派模型在JDK1.2 期间被引入并被广泛应用于之后的所有Java程序中,但并不是个强制性的约束模型,而是Java设计者推荐给开发者的一种类加载器实现方式。

双亲委派模型的工作过程:如果一个类加载器收到了类加载的请求,他首先不会自己去尝试加载这个类,而是把这个请求委派父类加载器去完成。每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个请求时,子加载器才会尝试自己去加载。
在这里插入图片描述

3、双亲委派好处

  • 避免类的重复加载,确保一个类的全局唯一性。Class与加载它的类加载器一起具备了一种带有优先级的层次关系,当父亲已经加载了该类时,就没有必要子ClassLoader 再加载一次;
  • 保护程序安全,防止核心API被随意篡改。

4、双亲委派实现

双亲委派模型的实现在 java.lang.ClassLoader 的 loadClass() 方法之中:

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{synchronized (getClassLoadingLock(name)) {// 检查是否已经被加载过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// 父类加载器无法成功加载}if (c == null) {// 父类加载器无法加载,调用自身的 findClass 方法进行加载long t1 = System.nanoTime();c = findClass(name);// 定义类加载器,记录相关数据sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) {resolveClass(c);}return c;}
}

逻辑清晰易懂:先检查是否已经被加载过,若没有加载则调用父加载器的 loadClass() 方法, 如父加载器为空则默认使用启动类加载器作为父加载器。如果父类加载失败,抛出ClassNotFoundException 异常后,再调用自己的 findClass() 方法进行加载。

5、破坏双亲委派模型

到目前为止(JDK8),双亲委派模型有过3次大规模的“被破坏”的情况。

第一次破坏

由于双亲委派模型是在JDK1.2之后才被引入的,而类加载器和抽象类 java.lang.ClassLoader 则在JDK1.0时代就已经存在,用户去继承 java.lang.ClassLoader 的唯一目的就是为了重写 loadClass() 方法,因为虚拟机在进行类加载的时候会调用加载器的私有方法 loadClassInternal(),而这个方法唯一逻辑就是去调用自己的 loadClass() 。为了兼容这些已有代码,Java设计者引入双亲委派模型时不得不做出一些妥协,在JDK1.2之后的 java.lang.ClassLoader 中添加了一个新的 protected 方法 findClass() ,并引导用户编写类加载逻辑时,尽可能去重写这个方法,而不是在 loadClass() 中编写代码。

第二次破坏

双亲委派模型的第二次“被破坏”是由这个模型自身的缺陷所导致的,双亲委派很好地解决了各个类加载器协作时基础类型的一致性问题(越基础的类由越上层的加载器进行加载),基础类之所以称为“基础”,是因为它们总是作为被用户代码继承,调用的API存在。但是如果基础类又要调用回用户的代码,那该么办?

一个典型的例子就是JNDI服务,JNDI现在已经是Java的标准服务,它的代码由启动类加载器去加载(在JDK1.3时放进去的 rt.jar),但JNDI的目的就是对资源进行集中管理和查找,它需要调用由独立厂商实现并部署在应用程序的 ClassPath 下的JNDI接口提供者的代码,但启动类加载器不可能“认识”这些代码。

为了解决这个问题,Java设计团队只好引入了一个不太优雅的设计:线程上下文类加载器(Thread Context ClassLoader)。这个类加载器可以通过 java.lang.Thread 类的setContextClassLoader() 方法进行设置,如果创建线程时还未设置,他将会从父线程中继承一个,如果在应用程序的全局范围内都没有设置过的话,那这个类加载器默认就是应用程序类加载器。

有了线程上下文加载器,JNDI服务就可以使用它去加载所需要的SPI代码,也就是父类加载器请求子类加载器去完成类加载的动作,这种行为实际上就是打通了双亲委派模型层次结构来逆向使用类加载器,实际上已经违背了双亲委派模型的一般性原则。Java中所有涉及SPI的加载动作基本上都采用这种方式,例如JNDI、JDBC、JCE、JAXB和JBI等。

第三次破坏

双亲委派模型的第三次“被破坏”是由于用户对程序动态性的追求导致的,这里所说的“动态性”指的是当前一些非常“热门”的名词:代码热替换、模块热部署等,简单的说就是机器不用重启,只要部署上就能用。

OSGi实现模块化热部署的关键则是它自定义的类加载器机制的实现。每一个程序模块(Bundle)都有一个自己的类加载器,当需要更换一个 Bundle 时,就把 Bundle 连同类加载器一起换掉以实现代码的热替换。在OSGi环境下,类加载器不再是双亲委派模型中的树状结构,而是进一步发展为更加复杂的网状结构,当受到类加载请求时,OSGi将按照下面的顺序进行类搜索:

  • 将java.*开头的类委派给父类加载器加载;
  • 否则,将委派列表名单内的类委派给父类加载器加载;
  • 否则,将 Import 列表中的类委派给 Export 这个类的 Bundle 的类加载器加载;
  • 否则,查找当前 Bundle 的 ClassPath,使用自己的类加载器加载;
  • 否则,查找类是否在自己的 Fragment Bundle 中,如果在,则委派给 Fragment Bundle 的类加载器加载;
  • 否则,查找 Dynamic Import 列表的 Bundle,委派给对应 Bundle 的类加载器加载;
  • 否则,类加载器失败。

6、如何破坏双亲委派模型?

  • 使用SPI机制;
  • 自定义类继承 ClassLoader,作为自定义类加载器,重写 loadClass() 方法,不让它执行双亲委派逻辑,从而打破双亲委派。但是遇到自定义类加载器和核心类重名或者篡改核心类内容,jvm会使用沙箱安全机制,保护核心类,防止打破双亲委派机制,防篡改,如果重名的话就报异常。

三、有哪些破坏双亲委派模型的例子?

1、SPI

spi机制是一种服务发现机制。它通过在 ClassPath 路径下的 META-INF/services 文件夹查找文件,自动加载文件里所定义的类。这一机制为很多框架扩展提供了可能,比如在JDBC中就使用到了SPI机制。

原生的JDBC中 Driver 驱动本身只是一个接口,并没有具体的实现,具体的实现是由不同数据库厂商去实现的。原生的JDBC中的类是放在 rt.jar 包的,是由启动类加载器进行类加载的,在JDBC中的 Driver 类中需要动态加载不同数据库类型的 Driver 类,而 mysql-connector-.jar 中的 Driver 类是用户自己写的代码,那启动类加载器肯定是不能进行加载的,于是乎,这个时候就引入SPI,程序就可以把原本需要由启动类加载器进行加载的类,由应用程序类加载器去进行加载了。

2、Tomcat

2.1、Tomcat为什么不使用默认的双亲委派模型?

Tomcat 作为一个 web 容器,存在以下使用场景:

  1. 部署两个应用程序,不同的应用程序可能会依赖同一个第三方类库的不同版本,不能要求同一个类库在同一个服务器只有一份,因此要保证每个应用程序的类库都是独立的,保证相互隔离;
  2. 部署在同一个 web 容器中相同的类库相同的版本可以共享;
  3. 容器也有自己依赖的类库,不能与应用程序的类库混淆。基于安全考虑,应该让容器的类库和程序的类库隔离开来;
  4. 要支持 jsp 的热部署(jsp 文件最终也是编译成 class 文件才能在虚拟机中运行)。

对于第一种和第三种场景,如果使用默认的类加载器机制,是无法加载两个相同类库的不同版本的,默认的类加载器只关注全限定类名,不关注是什么版本的。
第二种场景,默认的类加载器是能够实现的,因为他的职责就是保证唯一性。
第四种场景,要实现 jsp 文件的热更新(jsp 文件其实也就是 class 文件),使用默认类加载器,如果修改了,但类的全限定名还是一样,类加载器会直接取方法区中已经存在的,修改后的 jsp 是不会重新加载的。

2.2、Tomcat 如何实现自己的类加载机制?

Tomcat 自己实现了自己的类加载器:

  • CommonLoader:Tomcat最基本的类加载器,加载路径中的 class 可以被Tomcat容器本身以及各个 Webapp 访问;
  • CatalinaLoader:Tomcat容器私有的类加载器,加载路径中的 class 对于 Webapp 不可见;
  • SharedClassLoader:各个 Webapp 共享的类加载器,加载路径中的 class 对于所有 Webapp可见,但是对于Tomcat容器不可见;
  • WebappClassLoader:各个 Webapp 私有的类加载器,加载路径中的 class 只对当前 Webapp可见;
  • JspClassLoader:每一个JSP文件对应一个Jsp类加载器。
    在这里插入图片描述

从图中的委派关系中可以看出:

  1. CommonClassLoader 能加载的类都可以被 CatalinaClassLoader 和 SharedClassLoader 使用,从而实现了公有类库的共用;
  2. CatalinaClassLoader 和 Shared ClassLoader 自己能加载的类则与对方相互隔离;
  3. WebAppClassLoader 可以使用 SharedClassLoader 加载到的类,但各个WebAppClassLoader 实例之间相互隔离;
  4. JasperLoader 的加载范围仅仅是这个JSP文件所编译出来的那一个 .Class 文件,它出现的目的就是为了被丢弃:当Web容器检测到JSP文件被修改时,会替换掉目前的 JasperLoader 的实例,并通过再建立一个新的 JasperLoader 来实现JSP文件的热插拔功能。

四、参考资料

《深入理解java虚拟机》


http://chatgpt.dhexx.cn/article/dX4TB707.shtml

相关文章

类加载机制(整个过程详解)

一:背景 类加载机制是在我们的真个java的运行阶段中的其中一个阶段。 二:什么是快乐星球(类加载机制) 我们编写的 Java 文件都是以.java 为后缀的文件&#xff0c;编译器会将我们编写的.java 的文件编译成.class 文件&#xff0c;简单来说类加载机制就是jvm从文件系统将一系…

TCP/IP四层模型---应用层

相对于OSI七层模型,TCP/IP四层模型更为简化,总结为应用层,传输层,网络层,数据链路层四层 简单来说,四层协议分别为: 协议 应用层DNS,URI,HTML,HTTP,SSL,SMTP,POP,IMAPTELNET,SSH,FTP,SNMP应用程序相关传输层TCP,UDP,UDP-Lite,SCTP,DCCP操作系统内核负责网络层ARP,IP,ICMP数据链…

OSI七层网络模型与TCP/IP四层网络模型

OSI模型&#xff0c;即开放式通信系统互联参考模型(Open System Interconnection,OSI/RM,Open Systems Interconnection Reference Model)&#xff0c;是国际标准化组织(ISO)提出的一个试图使各种计算机在世界范围内互连为网络的标准框架&#xff0c;简称OSI。 OSI网络模型按照…

TCP/IP五层(四层)模型

TCP/IP五层&#xff08;四层&#xff09;模型中包含的协议有很多&#xff0c;其中典型代表是TCP协议和IP协议。 应用层&#xff1a;应用程序直接打交道的协议。在实际开发中涉及最多的部分&#xff0c;甚至需要自己设计应用层协议。 传输层&#xff1a;负责端到端之间的传输。&…

【计算机网络】TCP/IP四层模型和OSI七层模型

文章目录 一、TCP/IP四层模型和OSI七层模型关系二、TCP/IP四层模型三、OSI七层模型四、GET和POST的区别五、从输入网址到页面展示的过程六、详细过程 一、TCP/IP四层模型和OSI七层模型关系 1、OSI引进了服务、接口、协议、分层的概念&#xff0c;TCP/IP借鉴了OSI的这些概念建立…

TCP/IP四层模型与OSI七层模型

1&#xff09;网络协议 【网络协议】是【网络上所有设备】&#xff08;网络服务器、计算机及交换机、路由器、防火墙等&#xff09;之间【通信规则】的【集合】&#xff0c;它规定了进行【网络中的对等实体数据交换】而建立的规则。由于大多数网络采用【分层的体系结构】&…

OSI七层模型和TCPIP四层网络模型

OSI七层模型和TCP/IP四层网络模型 写在文前&#xff0c;本篇文章是在学习过程抄录的笔记&#xff0c;需要更详细的内容可以在微信搜索javaguide公众号获取。1、OSI划分 七层划分&#xff1a;应用层、表示层、会话层、传输层、网络层、数据链路层、物理层五层划分&#xff1a;…

读懂TCP IP四层模型与OSI七层模型

目录 一、OSI七层模型 二、TCP/IP四层模型 三、OSI模型的七层解读 3.1 OSI七层模型小结 3.2 TCP/IP模型与OSI模型的对应关系 四、常见的网络相关协议 五、TCP和UDP协议 5.1 TCP三次握手 5.2 TCP四次挥手 5.3 UDP协议 5.4 TCP和UDP的区别 六、TCP/IP四层模型与OSI七…

Linux网络_从系统到网络(网络协议栈分层与功能,认识协议,OSI七层模型与TCPIP四层模型,网络通信流程(局域网,跨网络),MAC地址,IP地址)

文章目录 1.网络在体系结构的位置与网络协议栈的层状结构2.协议栈各层的功能3.协议4.OSI七层模型与TcpIp四层模型5.网络通信的流程局域网通信流程(以太网)MAC地址跨网络通信IP地址 1.网络在体系结构的位置与网络协议栈的层状结构 2.协议栈各层的功能 协议栈分层设计达到了解耦目…

TCP/IP五层(或四层)模型

图解TCP/IP协议 基本概念 1、TCP连接 手机能够使用联网功能是因为手机底层实现了TCP/IP协议&#xff0c;可以使手机终端通过无线网络建立TCP连接。TCP协议可以对上层网络提供接口&#xff0c;使上层网络数据的传输建立在“无差别”的网络之上。 建立起一个TCP连接需要经过“三…

matlab怎么画两个自变量的图_tcpip四层模型怎么画?画模型图的好用软件推荐

tcpip四层模型就是一个将osi参考模型的会话层和表示层合并到应用层&#xff0c;数据链路层和物理层合并为链路层后的结果。tcpip四层模型包含应用层、运输层、网络层和链路层。tcpip四层模型的协议功能包括数据的发送、与硬件的交互、消息路由规则、格式定义、错误验证。 tcpip…

TCP/IP四层模型与OSI七层参考模型(网络协议)

TCP/IP四层模型与OSI七层参考模型 一. OSI七层参考模型①. 物理层②. 数据链路层③. 网络层④. 传输层⑤. 会话层⑥. 表示层⑦. 应用层 二. TCP/IP模型①. 物理层②. 数据链路层---网络协议③. 网络层---网络协议④. 传输层---网络协议⑤. 应用层---网络协议 三 .TCP/IP四层模型…

网络分层(OSI7层模型和TCP/IP四层模型)

1 OSI七层模型 1.1 物理层 主要定义物理设备标准&#xff0c;如网线的接口类型、光纤的接口类型、各种传输介质的传输速率等。它的主要作用是传输比特流&#xff08;就是由1、0转化为电流强弱来进行传输&#xff0c;到达目的地后再转化为1、0&#xff0c;也就是我们常说的数模…

TCPIP四层模型和OSI七层模型对应表

转载于:https://www.cnblogs.com/Pual623548198/p/7084421.html

计算机网络--TCP/IP四层模型

TCP/IP四层模型 在了解TCP/IP四层模型前,我们先来了解一下OSI七层结构。 OSI七层模型 虽然OSI七层模型划分的很完美,但是在实际应用当中有一些层的工作是重复的,所以出现了更贴近实际的TCP/IP四层模型。TCP/IP四层模型其实也就是将一些出现重复工作的层进行合并。比如将数…

计算机网络-OSI七层协议模型、TCPIP四层模型和五层协议体系结构之间的关系各层的作用

文章目录 一、结构二、对应的协议三、各层的作用1、物理层2、数据链路层3、网络层4、运输层5、会话层6、表示层7、应用层 一、结构 二、对应的协议 三、各层的作用 1、物理层 主要定义物理设备标准&#xff0c;如网线的接口类型、光纤的接口类型、各种传输介质的传输速率等。…

TCPIP四层协议

TCP/IP四层协议 在说TCP/IP四层协议之前&#xff0c;就不得不说OSI七层模型 OSI七层模型&#xff1a;自底向上依次是物理层&#xff0c;数据链路层&#xff0c;网络层&#xff0c;传输层&#xff0c;会话层&#xff0c;表示层&#xff0c;应用层 TCP/IP体系结构的优点&#x…

TCP/IP协议四层模型

TCP&#xff08;传输控制协议&#xff09;/IP&#xff08;网际协议&#xff09; 简介 TCP/IP协议是一系列网络协议的总和&#xff1b;包括&#xff1a;TCP&#xff0c;IP&#xff0c;UDP&#xff0c;ARP等&#xff0c;这些被称为子协议。在这些协议中&#xff0c;最重要、最著名…

深入理解网络通信协议一之TCPIP四层模型

快速理解网络通信协议 5.1五层模型 物理层&#xff1a;把主机连接起来的物理手段&#xff0c;作用是负责传送01电信号 链路层&#xff1a;确定物理层上01信号的分组方式&#xff1b;一组电信号称为帧&#xff0c;一个帧分成两部分&#xff1a;标头&#xff08;head&#xff0…