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 loader = ServiceLoader.load(Driver.class);
        Iterator 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。

你可能感兴趣的:(10_JVM学习笔记_线程上下文类加载器分析)