前言

目前学习到的类加载的知识,都是基于【双亲委托机制】的。那么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. vue 嵌入倒计时组件( 亲测可用 )

    由于花费了我不少时间才找到的组件,所以记录一下,后面方便自己好找一些,也算是分享出来给各位前端一起用. npm 下载npm install vue2-flip-countdown --save tem ...

  2. 如何实现LRU缓存?

    面试官:来了,老弟,LRU缓存实现一下? 我:直接LinkedHashMap就好了. 面试官:不要用现有的实现,自己实现一个. 我:..... 面试官:回去等消息吧.... 大家好,我是程序员学长,今 ...

  3. Java clone() 方法克隆对象——深拷贝与浅拷贝

    基本数据类型引用数据类型特点 1.基本数据类型的特点:直接存储在栈(stack)中的数据 2.引用数据类型的特点:存储的是该对象在栈中引用,真实的数据存放在堆内存里 引用数据类型在栈中存储了指针,该指 ...

  4. Docker数据映射

    1.映射目录 docker run -v 2.映射文件 docker run -v

  5. Java-Bean Validation后端校验总结

    Validation Information resource: SpringBoot Docs: 2.8.9. @ConfigurationProperties Validation url: ht ...

  6. 植入式Web前端开发方法

    上一篇,我讲述了植入式Web前端开发的基本情况,本篇就来探究其开发方法.以下假定CMS只能植入前端代码,并且需求规模是任意大小的. 代码形式 HTML代码是直接植入的毫无疑问,但除非植入的代码非常简短 ...

  7. 【MyBatis】几种批量插入效率的比较

    批处理数据主要有三种方式: 反复执行单条插入语句 foreach 拼接 sql 批处理 一.前期准备 基于Spring Boot + Mysql,同时为了省略get/set,使用了lombok,详见p ...

  8. 面试官:MySQL的幻读是怎么被解决的?

    大家好,我是小林. 我之前写过一篇数据库事务的文章「 事务.事务隔离级别和MVCC」,这篇我说过什么是幻读. 在这里插入图片描述 然后前几天有位读者跟我说,我这个幻读例子不是已经被「可重复读」隔离级别 ...

  9. 机器学习——集成学习(Bagging、Boosting、Stacking)

    1 前言 集成学习的思想是将若干个学习器(分类器&回归器)组合之后产生一个新学习器.弱分类器(weak learner)指那些分类准确率只稍微好于随机猜测的分类器(errorrate < ...

  10. 洛谷P1582——倒水(进制,数学)

    https://www.luogu.org/problem/show?pid=1582 题目描述 一天,CC买了N个容量可以认为是无限大的瓶子,开始时每个瓶子里有1升水.接着~~CC发现瓶子实在太多了 ...