【Java虚拟机11】线程上下文类加载器

摘要:
JDK为我们提供了一种打破双亲委托模型的机制:线程上下文类加载器。所以这些JDK所提供的标准接口的Class文件是被启动类加载器所加载的。如果没有通过setContextClassLoader进行设置的话,线程将继承其父线程的上下文类加载器。JAVA应用运行时的初始线程的上下文类加载器是系统类加载器。
前言

目前学习到的类加载的知识,都是基于【双亲委托机制】的。那么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】线程上下文类加载器》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇Gerapy框架的安装获取当前时间并格式化输出、时间戳转标准格式下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

CPU部分总结

一. CPU性能指标1. 首先,最容易想到的是CPU使用率,也是实际环境中最常见的一个性能指标 CPU使用率是非空闲时间占总CPU时间的百分比,根据CPU上运行任务的不同,又被分为用户CPU,系统CPU,等待I/O CPU,软中断和硬中断 用户CPU使用率:包括用户态CPU使用率(user)和低优先级用户态CPU使用率(nice),表示CPU在用户态运行的...

Nginx超时配置

Nginx超时配置1.client_header_timeout语法client_header_timeout time 默认值60s 上下文http server 说明指定等待client发送一个请求头的超时时间(例如:GET/HTTP/1.1).仅当在一次read中,没有收到请求头,才会算超时。如果在超时时间内,client没发送任何东西,nginx返...

中断上下文

1.进程上下文: (1)进程上文:其是指进程由用户态切换到内核态是需要保存用户态时cpu寄存器中的值,进程状态以及堆栈上的内容,即保存当前进程的进程上下文,以便再次执行该进程时,能够恢复切换时的状态,继续执行。 (2)进程下文:其是指切换到内核态后执行的程序,即进程运行在内核空间的部分。 2.中断上下文: (1)中断上文:硬件通过中断触发信号,导致内核调用...

SpringApplication类-1

SpringApplication类提供了一种方便的方法来引导从main()方法启动的Spring应用程序。在许多情况下,可以委托给静态SpringApplication.run方法,如下例所示: public static voidmain(String[] args) { SpringApplication.run(MySpringConfig...

深入浅出 妙用Javascript中apply、call、bind

apply、call、bind的认识,并且列出一些它们的妙用加深记忆。 apply、call 在 javascript 中,call 和 apply 都是为了改变某个函数运行时的上下文(context)而存在的,换句话说,就是为了改变函数体内部 this 的指向。 JavaScript 的一大特点是,函数存在「定义时上下文」和「运行时上下文」以及「上下文...

iOS绘图教程

本文转载至http://blog.csdn.net/pjk1129/article/details/12783677 原贴地址:http://www.cnblogs.com/xdream86/archive/2012/12/12/2814552.html 本文是《Programming iOS5》中Drawing一章的翻译,考虑到主题完整性,翻译版本中...