前言

目前学习到的类加载的知识,都是基于【双亲委托机制】的。那么JDK难道就没有提供一种打破双亲委托机制的类加载机制吗?

答案是否定的。

JDK为我们提供了一种打破双亲委托模型的机制:线程上下文类加载器。

1.引出【线程上下文类加载器】

我们来复习一下JDBC执行SQL的代码【画外音:感觉回到了大学自学Java的时候】:

Connection conn = DriverManager.getConnection(url, user, password);
String sql = "insert into user (name, pwd) values(?,?)";
Statement st = conn.createStatement();
st.executeQuery(sql);

重点关注一下接口Connection,Statement接口,大家都很清楚,这2个接口是JDK提供给各个数据库厂商的通过JAVA访问数据库的接口。

而这些接口是在包java.sql里面,而这个包是在rt.jar这个包中。所以这些JDK所提供的标准接口的Class文件是被启动类加载器所加载的。

而数据库厂商:如MySQL/Oracle根据Java语言的标准所提供的驱动包,如:mysql-connector-java-5.1.38.jar,这些驱动包一般在项目中都仅仅是在classpath目录下,

并不能被启动类加载器加载,只能被系统类加载器加载。

那么问题来了:Connection conn = DriverManager.getConnection(url, user, password);

方法返回的实例其实是一个com.mysql.jdbc.ConnectionImpl(应该被AppClassLoader加载),

而方法返回值的定义为java.sql.Connection(应该被BootstrapClassLoader加载)。

根据前面学习的命名空间的逻辑:“由父类加载器加载的类不能看见子加载器加载的类”,按这一规则来说,获取Connection的代码是不能执行成功的。

其实JDK已经想到并帮我们处理了这个问题。

它使用了一种叫做线程上下文类加载器的CLassLoader机制,来打破双亲委托机制在某些场景无法满足扩展的需求,实现这种场景的应用。

2.概念

2.1当前类加载器(Current Class Loader)

每个类都会使用自己的类加载器(即加载自身的类加载器)去加载其他类(指的是所依赖的类),

如果ClassX引用了ClassY,那么ClassX的类加载器就会先去加载ClassY(前提是ClassY尚未被加载过)。

2.2线程上下文类加载器(Thread Context Class Loader)

线程上下文类加载器是从JDK1.2开始引入的,类Thread中的getContextClassLoader()与setContextClassLoader(ClassLoader cl)分别用来获取和设值上下文类加载器。

如果没有通过setContextClassLoader(ClassLoader cl)进行设置的话,线程将继承其父线程的上下文类加载器。

JAVA应用运行时的初始线程的上下文类加载器是系统类加载器(AppClassLoader)。在线程中运行的代码可以通过该类加载器来加载类与资源。

线程上下文类加载器就是当前线程的Current Class Loader

2.3线程上下文类加载器是如何打破双亲委托模型的

SPI (Service Provider Interface)

父classloader可以使用当前线程Thread.currentThread().getContextLoader()所指定的classloader加载的类。

这就改变了父classloader不能使用子classloader或是其他没有直接父子关系的classloader加载类的情况,即改变了双亲委托模型。

在双亲委托模型下,类加载是由下至上的,即下层的加载器会委托上层进行加载,但是对于SPI来说,有些接口是JAVA核心库所提供的。

而JAVA核心库是由启动类加载器来加载的,而这些接口的实现是来自于不同的jar包(厂商的提供),JAVA的启动类加载器是不会加载其他来源的jar包。

这样传统的双亲委托模型就无法满足SPI的要求。

而通过给当前线程设置上下文类加载器就可以由设置的上下文类加载器来实现对接口实现类的加载。(打破双亲委托模型限制)

3.默认的线程上下文类加载器

3.1 Launcher实例化时,默认设置线程上下文类加载器

在上次的文章【Java虚拟机10】ClassLoader.getSystemClassLoader()流程简析第二步中,简要介绍了Launcher实例的初始化过程,其中有一句代码当时是一笔带过的,这次拿出来再看看。

就是在Launcher类构造方法,首先初始化了ExtClassLoader,然后初始化了AppClassLoader,之后调用了

    public Launcher() {
//balabala.....
Thread.currentThread().setContextClassLoader(this.loader);
//balabala.....
}

3.2 ClassLoader.getSystemClassLoader()时,默认设置线程上下文类加载器

依然在上一篇文章【Java虚拟机10】ClassLoader.getSystemClassLoader()流程简析第四步中,也存在一个设置默认的线程上下文类加载器的代码。

可以看出,虚拟机默认的线程上下文类加载器为系统类加载器(AppClassLoader)

4.线程上下文类加载器的一般使用模式

线程上下文类加载器的一般使用模式为:

ClassLoader originClassLoader = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(myClassLoader);
myMethod();
//myMethod里面则调用了Thread.currentThread().getContextClassLoader(),用外面设置的加载器(myClassLoader)去完成一些自己希望完成的事情。
} finally {
Thread.currentThread().setContextClassLoader(originClassLoader);
}

5.总结

  • 如果一个类由类加载器A加载,那么这个类的依赖类也会被类加载器A加载(前提是这个依赖类尚未被加载过)。
  • ThreadContextClassLoader的存在就是为了破坏双亲委托模型。
  • 当高层提供了统一的接口让低层去实现,同时又要在高层去加载(或实例化)低层的类的时候,就必须用ThreadContextClassLoader来帮助高层的ClassLoader来找到并加载低层类。

【Java虚拟机11】线程上下文类加载器的更多相关文章

  1. 深入理解Java类加载器(二):线程上下文类加载器

    摘要: 博文<深入理解Java类加载器(一):Java类加载原理解析>提到的类加载器的双亲委派模型并不是一个强制性的约束模型,而是Java设计者推荐给开发者的类加载器的实现方式.在Java ...

  2. 线程上下文类加载器ContextClassLoader内存泄漏隐患

    前提 今天(2020-01-18)在编写Netty相关代码的时候,从Netty源码中的ThreadDeathWatcher和GlobalEventExecutor追溯到两个和线程上下文类加载器Cont ...

  3. JVM 线程上下文类加载器

    当前类加载器(Current ClassLoader) 每个类都会使用自己的类加载器(即加载自身的类加载器)来去加载其他类(指所依赖的类) 如果ClassX引用了ClassY,那么ClassX的类加载 ...

  4. 【JVM学习笔记】线程上下文类加载器

    有许多地方能够看到线程上下文类加载的设置,比如在sun.misc.Launcher类的构造方法中,能够看到如下代码 先写一个例子建立感性认识 public class Test { public st ...

  5. 7. 通过JDBC源码来分析线程上下文类加载器以及SPI的使用

    目录 1. 什么是全盘负责委托机制 2. 为什么需要有线程上下文类加载器 2.1 使用JDBC的例子,分析为什么双亲委托机制不能实现要求 2.2 线程上下文类加载器的作用 3. 线程上下文类加载器的使 ...

  6. 线程上下文类加载器(Context ClassLoader)

    1.线程上下文类加载器是从jdk1.2开始引入的,类Thread中的getContextClassLoader()与setContextClassLoader(ClassLoader c1),分别用来 ...

  7. Java虚拟机JVM学习05 类加载器的父委托机制

    Java虚拟机JVM学习05 类加载器的父委托机制 类加载器 类加载器用来把类加载到Java虚拟机中. 类加载器的类型 有两种类型的类加载器: 1.JVM自带的加载器: 根类加载器(Bootstrap ...

  8. 通过JDBC驱动加载深刻理解线程上下文类加载器机制

    关于线程上下文类加载器已经在之前学得比较透了,作为一个收尾,这里用平常J2EE开发时JDBC连接Mysql数据库常见的一段代码通过分析它的底层进一步加深对线程上下文类加载器的理解,所以先来将连接应用代 ...

  9. 【java虚拟机系列】JVM类加载器与ClassNotFoundException和NoClassDefFoundError

    在我们日常的项目开发中,会经常碰到ClassNotFoundException和NoClassDefFoundError这两种异常,对于经验足够的工程师而言,可能很轻松的就可以解决,但是却不一定明白为 ...

随机推荐

  1. layui的CRUD案列

    用layui来实现一个简单的二级权限和增删改查案列 利用layui提供的组件(table , layer , form,tree)来进行开发 写一个简单的登录界面   根据用户的ID来 获取用户所对应 ...

  2. WEB漏洞——XSS

    跨站脚本( Cross-site Scripting,简称为XSS或跨站脚本或跨站脚本攻击)是一种针对网站应用程序的安全漏洞攻击技术,是代码注入的一种. XSS攻击可以分为三种:反射型.存储型和DOM ...

  3. epoll代码框架

    epoll代码实现框架: #define MAX_EVENTS 10 struct epoll_event ev, events[MAX_EVENTS]; int listen_sock, conn_ ...

  4. Servlet生命周期和方法

    一.五个生命周期方法,有三个很重要,初始化方法.提供服务方法和销毁方法 1.三个主要方法 2.另外两个重写的成员方法只做了解 二.生命周期详解 其中,每次刷新页面都是一次对servlet访问: 页面访 ...

  5. 【第二十篇】-Maven IntelliJ之Spring Cloud直播商城 b2b2c电子商务技术总结

    Maven IntelliJ IntelliJ IDEA 已经内建了对 Maven 的支持.我们在此例中使用的是 IntelliJ IDEA 社区版 11.1. IntelliJ IDEA 的一些特性 ...

  6. 制作Windows服务和安装程序(C#版)

    http://blog.sina.com.cn/s/blog_5f4ffa170100vt2b.html 1.创建服务项目: 打开VS 2005 编程环境,在C#中新建Windows服务程序 2.将安 ...

  7. 数据结构逆向分析-List

    数据结构逆向分析-List 首先STL中的List就是一个链表,但是肯定C++用了很多封装,所以这里我们来一探究竟. 开始 首先先写一些简单的分析的源代码: #include<iostream& ...

  8. Java技术开发专题系列之【Guava RateLimiter】针对于限流器的入门到精通(针对于源码分析介绍)

    Guava包中限流实现分析 RateLimiter 之前的文章中已经介绍了常用的限流算法,而google在Java领域中使用Guava包中的限流工具进行服务限流. 回顾使用案例 Google开源工具包 ...

  9. PHP大文件读取操作

    简单的文件读取,一般我们会使用 file_get_contents() 这类方式来直接获取文件的内容.不过这种函数有个严重的问题是它会把文件一次性地加载到内存中,也就是说,它会受到内存的限制.因此,加 ...

  10. linux中如何查看文件上下文

    grep -C 10 keyword catalina.out filename https://blog.csdn.net/weixin_34791683/article/details/11660 ...