JVM工作原理与实战(十三):打破双亲委派机制-线程上下文类加载器

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
云数据库 RDS MySQL,高可用系列 2核4GB
简介: JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了打破双亲委派机制的方法、线程上下文类加载器等内容。

一、打破双亲委派机制的方法

双亲委派机制的核心思想是:当一个类加载器接收到加载类的请求时,它首先不会自己去尝试加载,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器(Bootstrap ClassLoader)中去,只有当父类加载器无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。

当一个类加载器接收到加载类的任务时,会自底向上查找是否加载过再由顶向下进行加载。

image.gif

双亲委派机制是Java类加载器的重要特性,但在某些情况下,可能需要打破这种机制。以下是打破双亲委派机制的方法:

  • 自定义类加载器:在Java中,可以通过继承ClassLoader并重写其loadClass方法来创建自定义类加载器。通过这种方式,可以打破双亲委派机制,实现类的隔离。例如,在Tomcat中,每个Web应用都有自己的类加载器,从而实现了应用之间的类隔离。当两个Web应用中有相同限定名的类时,如Servlet类,Tomcat通过自定义类加载器保证它们是不同的类。
  • 线程上下文类加载器:在Java中,每个线程都有一个关联的上下文类加载器。通过设置线程的上下文类加载器,可以实现类的加载。例如,JDBC和JNDI等就是利用线程上下文类加载器来加载类的。
  • Osgi框架的类加载器:Osgi框架实现了一套新的类加载器机制,允许同级之间委托进行类的加载。在Osgi中,每个Bundle都有自己的类加载器,当需要加载类时,会先在自己的存储空间中查找,如果找不到,则委托给父级Bundle的类加载器进行查找。这种机制打破了传统的双亲委派模型。

二、线程上下文类加载器

双亲委派机制是Java类加载器的一个重要特性,它保证了Java核心API的稳定性和防止了类冲突。然而,在某些情况下,可能需要打破这种机制。接下来将重点探讨如何通过线程上下文类加载器来实现这一点,并以JDBC中的驱动加载为例进行深入解析。

线程上下文类加载器(Thread Context ClassLoader,TCCL)是Java中一种特殊的类加载器,用于处理多线程环境下的类和资源加载问题。它确保了每个线程都能维护自己的类加载器上下文,从而实现了线程安全。线程上下文类加载器是解决线程局部变量和类加载问题的有效机制。当线程需要加载类时,它会首先使用与其关联的上下文类加载器,从而确保了类加载的一致性和正确性。

1.JDBC线程上下文类加载器案例

在JDBC中,DriverManager用于管理不同数据库的驱动。例如,MySQL的驱动和Oracle的驱动。DriverManager类位于rt.jar包中,由启动类加载器加载。然而,用户JAR包中的驱动需要由应用程序类加载器来加载,这就违反了双亲委派机制。

image.gif

为了解决这个问题,JDBC采用了SPI(Service Provider Interface)机制。SPI是一种JDK内置的服务提供发现机制。

在ClassPath路径下的META-INF/services文件夹中,以接口的全限定名来命名文件名,对应的文件中写该接口的实现:

例如,在MySQL JDBC驱动中,有一个名为java.sql.Driver的文件在META-INF/services文件夹中,文件内容为com.mysql.cj.jdbc.Driver。这样,当DriverManager需要加载MySQL驱动时,它会首先查看当前线程的上下文类加载器(通常是应用程序类加载器)是否能加载这个驱动。由于驱动的实现类是由应用程序类加载器加载的,这就打破了双亲委派机制。

image.gif

image.gif

使用ServiceLoader加载实现类:

使用ServiceLoader加载对应的Driver对象:

ServiceLoader<Driver> serviceLoader = ServiceLoader.load(Driver.class);

image.gif

在这个过程中,DriverManager使用了SPI机制来加载jar包中对应的驱动类。SPI机制通过在META-INF/services目录下查找实现了特定接口的类的配置文件,并使用线程上下文类加载器来加载这些实现类。

image.gif

SPI机制中使用的线程上下文类加载器通常是应用程序类加载器。这是因为线程上下文类加载器是线程特有的,每个线程都可以关联一个不同的上下文类加载器。这使得SPI机制能够灵活地加载类,打破了双亲委派机制的限制。

在ServiceLoader的load方法中,通过获取当前线程的上下文类加载器来实现类的加载。

public static <S> ServiceLoader<S> load(Class<S> var0) {
        ClassLoader var1 = Thread.currentThread().getContextClassLoader();
        return load(var0, var1);
    }

image.gif

image.gif

为了验证线程上下文类加载器的使用,可以创建一个新的线程并在其中打印线程上下文类加载器的信息。

public class NewThreadDemo {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getContextClassLoader());
            }
        }).start();
    }
}

image.gif

运行结果(应用程序类加载器):

image.gif

完整流程分析

  1. 启动类加载器加载DriverManager。
  2. 在初始化DriverManager时,通过SPI机制加载jar包中的myql驱动。
  3. SPI中利用了线程上下文类加载器(应用程序类加载器)去加载类并创建对象。

image.gif

这种由启动类加载器加载的类,委派应用程序类加载器去加载类的方式,打破了双亲委派机制。


总结

JVM是Java程序的运行环境,负责字节码解释、内存管理、安全保障、多线程支持、性能监控和跨平台运行。本文主要介绍了打破双亲委派机制的方法、线程上下文类加载器等内容,希望对大家有所帮助。

相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。 &nbsp; 相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/mysql&nbsp;
相关文章
|
7月前
|
Arthas 监控 Java
Arthas thread(查看当前JVM的线程堆栈信息)
Arthas thread(查看当前JVM的线程堆栈信息)
1182 10
|
7月前
|
Oracle Java 关系型数据库
JVM深入原理(一+二):JVM概述和JVM功能
JVM全称是Java Virtual Machine-Java虚拟机JVM作用:本质上是一个运行在计算机上的程序,职责是运行Java字节码文件,编译为机器码交由计算机运行。
194 0
|
7月前
|
Arthas 存储 Java
JVM深入原理(三+四):JVM组成和JVM字节码文件
目录3. JVM组成3.1. 组成-运行时数据区3.2. 组成-类加载器3.3. 组成-执行引擎3.4. 组成-本地接口4. JVM字节码文件4.1. 字节码文件-组成4.1.1. 组成-基础信息4.1.1.1. 基础信息-魔数4.1.1.2. 基础信息-主副版本号4.1.2. 组成-常量池4.1.3. 组成-方法4.1.3.1. 方法-工作流程4.1.4. 组成-字段4.1.5. 组成-属性4.2. 字节码文件-查看工具4.2.1. javap4.2.2. jclasslib4.2.3. 阿里Arthas
128 0
|
7月前
|
存储 安全 Java
JVM深入原理(五):JVM组成和JVM字节码文件
类的生命周期概述:类的生命周期描述了一个类加载,使用,卸载的整个过类的生命周期阶段:类的声明周期主要分为五个阶段:加载->连接->初始化->使用->卸载,其中连接中分为三个小阶段验证->准备->解析。
107 0
|
7月前
|
Arthas Java 测试技术
JVM深入原理(六)(一):JVM类加载器
目录6. JVM类加载器6.1. 类加载器-概述6.2. 类加载器-执行流程6.3. 类加载器-分类(JDK8)6.3.1. JVM底层实现的类加载器6.3.1.1. 启动类加载器6.3.2. Java代码实现类的加载器6.3.2.1. 扩展类加载器6.3.2.2. 应用程序类加载器6.4. 类加载器-Arthas查看类加载器
130 0
|
7月前
|
Arthas 存储 算法
深入理解JVM,包含字节码文件,内存结构,垃圾回收,类的声明周期,类加载器
JVM全称是Java Virtual Machine-Java虚拟机JVM作用:本质上是一个运行在计算机上的程序,职责是运行Java字节码文件,编译为机器码交由计算机运行类的生命周期概述:类的生命周期描述了一个类加载,使用,卸载的整个过类的生命周期阶段:类的声明周期主要分为五个阶段:加载->连接->初始化->使用->卸载,其中连接中分为三个小阶段验证->准备->解析类加载器的定义:JVM提供类加载器给Java程序去获取类和接口字节码数据类加载器的作用:类加载器接受字节码文件。
660 55
|
2月前
|
存储 缓存 Java
我们来说一说 JVM 的内存模型
我是小假 期待与你的下一次相遇 ~
218 4
|
2月前
|
存储 缓存 算法
深入理解JVM《JVM内存区域详解 - 世界的基石》
Java代码从编译到执行需经javac编译为.class字节码,再由JVM加载运行。JVM内存分为线程私有(程序计数器、虚拟机栈、本地方法栈)和线程共享(堆、方法区)区域,其中堆是GC主战场,方法区在JDK 8+演变为使用本地内存的元空间,直接内存则用于提升NIO性能,但可能引发OOM。
|
8月前
|
Arthas 监控 Java
Arthas memory(查看 JVM 内存信息)
Arthas memory(查看 JVM 内存信息)
645 6
|
缓存 Prometheus 监控
Elasticsearch集群JVM调优设置合适的堆内存大小
Elasticsearch集群JVM调优设置合适的堆内存大小
2037 1

热门文章

最新文章