10_JVM学习笔记_线程上下文类加载器分析

线程上下文类加载器分析

示例代码

package com.leofight.jvm.classloader;

public class MyTest24 {

    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getContextClassLoader());
        System.out.println(Thread.class.getClassLoader());
    }
}


输出结果

sun.misc.Launcher$AppClassLoader@14dad5dc
null

当前类加载器(Current Classloader)

每个类都会使用自己的类加载器(即加载自身的类加载器)来去加载其他类(指的是所依赖的类),如ClassX引用了ClassY,那么ClassX的类加载器就会区加载ClassY(前提是ClassY尚未被加载)。

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

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

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

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

线程上下文类加载器的重要性

SPI(Service Provider Interface)

父ClassLoader可以使用当前线程Thread.currentThread().getContextClassLoader()所指定的classloader加载的类。这就改变了父ClassLoader不能使用子ClassLoader或者其他没有直接父子关系的ClassLoader加载的类的情况,即改变了双亲委托模型。

线程上下文类加载器就是当前线程的Current Classloader。

在双亲委托模型下,类加载是由下至上的,即上层的类加载器会委托上层进行加载。但是对于SPI来说,有些接口是Java核心库所提供的,而Java核心库是由启动类加载器来加载的,而这些接口的实现却来自于不同的jar包(厂商提供),Java的启动类加载器是不会加载器其他来源的jar包,这样传统的双亲委托模型就无法满足SPI的要求。而通过给当前线程设置上下文类加载器,就可以有设置的上下文类加载器来实现对于接口实现类的加载。

示例代码

package com.leofight.jvm.classloader;

public class MyTest25 implements Runnable {

    private Thread thread;

    public MyTest25() {
        thread = new Thread(this);
        thread.start();
    }

    @Override
    public void run() {
        ClassLoader classLoader = this.thread.getContextClassLoader();

        this.thread.setContextClassLoader(classLoader);

        System.out.println("Class: " + classLoader.getClass());
        System.out.println("Parent: " + classLoader.getParent().getClass());
    }

    public static void main(String[] args) {
        new MyTest25();
    }
}

输出

Class: class sun.misc.Launcher$AppClassLoader
Parent: class sun.misc.Launcher$ExtClassLoader

线程上下文类加载器的一般使用模式(获取 - 使用 - 还原)

ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

 try{
      Thread.currentThread().setContextClassLoader(targetTcc1);
      myMethod();
  }finally{
      Thread.currentThread().setContextClassLoader(classLoader);
  }

myMethod里面则调用了Thread.currentThread().getContextClassLoader(),获取当前线程的上下文类加载器做某些事情。

如果一个类由类加载器A加载,那么这个类的依赖类也是由相同的类加载器加载的(如果该依赖之前没有被加载过的话)

ContextClassLoader的作用就是为了破坏Java的类加载委托机制。

当高层提供了统一的接口让低层去实现,同时又要在高层加载(或实例化)低层的类时,就必须要通过线程上下文类加载器来帮助高层的ClassLoader找到并加载该类。

示例代码

package com.leofight.jvm.classloader;
import java.sql.Driver;
import java.util.Iterator;
import java.util.ServiceLoader;

public class MyTest26 {

    public static void main(String[] args) {
        ServiceLoader<Driver> loader = ServiceLoader.load(Driver.class);
        Iterator<Driver> iterator = loader.iterator();

        while (iterator.hasNext()) {
            Driver driver = iterator.next();

            System.out.println("driver: " + driver.getClass() + ", loader: " + driver.getClass().getClassLoader());
        }
        System.out.println("当前线程上下文类加载器: " + Thread.currentThread().getContextClassLoader());
        System.out.println("ServiceLoader的类加载器: " + ServiceLoader.class.getClassLoader());

    }
}


输出

driver: class com.mysql.jdbc.Driver, loader: sun.misc.Launcher$AppClassLoader@14dad5dc
driver: class com.mysql.fabric.jdbc.FabricMySQLDriver, loader: sun.misc.Launcher$AppClassLoader@14dad5dc
当前线程上下文类加载器: sun.misc.Launcher$AppClassLoader@14dad5dc
ServiceLoader的类加载器: null

为什么可以加载mysql驱动?主要是ServiceLoader,下面来看ServiceLoader的详细介绍

一个简单的服务提供者加载设施。

服务是已知的接口和抽象类的集合。一个服务提供者是一个服务的特定实现。服务提供者中的类通常实现服务中的接口和子类在服务本身中定义的类。服务提供者可以以扩展的形式安装在Java平台的实现中。也就是将jar文件放入任何常用的扩展名目录中。提供者也可以通过将它们添加到应用程序的类路径或通过其他特定于平台的方式提供。

为了加载,服务由单一类型表示,也就是单个接口或抽象类。(一个具体的类可以被使用,但是这不被推荐)。给定服务的提供者包含一个或多个具体类,这些类将这个服务类型扩展为数据和特定于提供者的代码。提供者类通常不是整个提供者本身,而是包含足够信息的代理,用于决定提供者是否能够满足特定的请求以及根据需求可以创建实际提供者的代码。提供者类的细节往往是高度服务特定的,没有一个单独的类或接口可以统一它们,所以这里没有定义这种类型。该工具强制执行的唯一要求是提供程序类必须具有零参数构造函数,以便在加载过程中实例化它们。

通过在资源目录 META-INF/services 中放置一个提供程序配置文件来标识服务提供者。该文件的名称是服务类型的完全限定的二进制名称。该文件包含具体提供程序类的完全限定二进制名称的列表,每行一个。每个名称周围的空格和制表符以及空白行将被忽略。注释字符是#\u0023;在每行上忽略第一个注释字符后面的所有字符.该文件必须以UTF-8编码。

如果一个特定的具体提供者类在多个配置文件中被命名,或者在同一个配置文件中命名超过一次,那么重复项将被忽略.命名一个特定提供者的配置文件不需要与提供者本身在同一个jar文件或其他分发单元中。提供程序必须可以从最初查询的相同类加载程序访问以查找配置文件;请注意,这不一定是文件实际加载的类加载器。

mysql的驱动在文件java.sql.Driver中。路径为mysql jar中的META-INF/ services路径下

com.mysql.jdbc.Driver
com.mysql.fabric.jdbc.FabricMySQLDriver

提供者懒惰地定位并实例化,即按需提供。, 一个服务加载器维护已经被加载的提供者的缓存。iterator方法的每次调用都会返回一个迭代器,该迭代器首先以实例化顺序生成所有缓存元素,然后懒惰地查找和实例化任何剩余的提供程序,并将每个提供程序添加到缓存中, 缓存可以通过eload方法清除。

服务加载器总是在调用者的安全上下文中执行。 , 受信任的系统代码通常应该在特权安全上下文中调用这个类中的方法,以及它们返回的迭代器的方法。

这个类的实例对于多个并发线程不安全。

除非另有说明,否则将null参数传递给此类中的任何方法将导致引发NullPointerException。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,657评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,889评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,057评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,509评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,562评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,443评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,251评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,129评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,561评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,779评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,902评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,621评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,220评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,838评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,971评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,025评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,843评论 2 354

推荐阅读更多精彩内容