深入理解JVM(二,JVM初识线程上下文类加载器和java字节码)

线程上下文类加载器的引入。
最好最常见的引入:对应得数据库连接包中
在这里插入图片描述

Class.forName("org.postgresql.Driver").newInstance(); 
String url ="jdbc:postgresql://localhost/myDB"
//myDB为数据库名 
String user="myuser"; 
String password="mypassword"; 
Connection conn= DriverManager.getConnection(url,user,password); 

通过上面可知,这些连接方式,肯定是最初已经加载了的,显然是bootstrap加载器已经将相关类加载。因此才能初始时我们可这样直接写(反问:什么都有依据,那么你凭什么就可以直接使用?原因就在早已经加载了Connection类,这些具体的定制都是由厂商制定的,我们只是将他放在了对应的位置,加载使用)
因此引入ContextClassLoader来破坏双亲委托机制,来实现上面的SPI加载。

package com.auto.demo;

/**
 * 当前类加载器(current classloader):每个都会尝试用自己的类加载器去加载其他类
* 这就是下面是null的原因;
 * ContextClassLoader(线程上下文加载器):Thread类中的getContextClassLoader与setContextClassLoader
 * 用于获取和设置上下文加载器。
 * 没有setContextClassLoader进行设置,默认线程将继承其父类的线程的上下文加载器。
 * java应运行时初始线程的上下文类加载器是系统类加载器。
 * 线程上下文加载器的重要性:
 * ①  SPI(Service Provide Interface)
 * ②  父ClassLoader可以使用当前线程Thread.currentThread.getContextoader()所指定的classloader加载的类。
 * 这就改变了父ClassLoader不能使用子ClassLoader或是其他没有直接父子关系的ClassLoader加载的类的情况,
 * 即是改变了双亲委托机制。
 * ③  在双亲委托机制模型下,来加载器是由上至下的,即下层的类加载会委托上层进行加载。但是对于SPI来说,有些接口是java核心
 * 库所提供的,而在java核心库是由启动类加载器加载的,而这些接口的实现却来自于不同的jar包(各大厂商提供),java的启动类加
 * 载器是不会加载其他来源的jar包的,这样传统的双亲委托模型就无法满足SPI的要求,而通过给当前线程设置上下文类加载器
 * 来实现对于接口实现类的加载。(框架开发极其常用)
 * @author zhouyi
 *
*/
public class MyContextTest {
public static void main(String[] args) {
    System.out.println(Thread.currentThread().getContextClassLoader()); // 系统类加载器
    System.out.println(Thread.class.getClassLoader()); // 是由启动类加载加载,因此是null
}
}

运行结果:
在这里插入图片描述

public class MyContextTest implements Runnable {

private Thread thread;

public MyContextTest() {
    thread = new Thread(this); // 因为是Runnable所以必须放入线程中
    thread.start();
}

@Override
public void run() {
    ClassLoader clLoader = this.thread.getContextClassLoader();
    this.thread.setContextClassLoader(clLoader);
    System.out.println("class: " + clLoader.getClass());
    System.out.println("parent: " + clLoader.getParent().getClass());
}
public static void main(String[] args) {
    new MyContextTest();
}
}

运行结果:
在这里插入图片描述
ServiceLoader
是服务提供者加载的集合,比如加载数据库连接,建议查看源码。

package com.auto.demo;

import java.sql.Driver;
import java.util.Iterator;
import java.util.ServiceLoader;

/**
*线程上下文类加载器的一般使用模式(获取 - 使用 - 还原)
*获取:ClassLoader clLoader = this.thread.getContextClassLoader();
*使用:
*try {
 * Thread.currentThread().setContextClassLoader(targetClassLoader);
 * myMethod(); // 我们自己要做操作的方法
 *} finally {
 *  Thread.currentThread().getContextClassLoader(clLoader); // 还原
 *}
 *myMethod():调用了Thread.currentThread().getContextClassLoader(targetClassLoader);获取当前线程的上下文类加载器做某些事。
 *
 *当高层提供了统一的接口让低层实现,同时又要在高层加载(或实例化)低层的类时,
 *就必须通过县城上下文类加载器来帮助高层的ClassLoader找到并加载。
 * @author zhouyi
 *
 */

public class MyContextTest {

public static void main(String[] args) {
    ServiceLoader<Driver> loader = ServiceLoader.load(Driver.class); // 拿到数据库连接驱动
    Iterator<Driver> it = loader.iterator();
    while (it.hasNext()) {
        Driver driver = it.next();
        System.out.println("driver: " + driver + ",loader: " + driver.getClass().getClassLoader());
    }
    System.out.println("当前线程上下文类加载器: " + Thread.currentThread().getContextClassLoader());
    System.out.println("service的类加载器: " + ServiceLoader.class.getClassLoader());
}
}

运行结果:
在这里插入图片描述

修改它的加载方式:(改为上层)

public class MyContextTest {

public static void main(String[] args) {
    Thread.currentThread().setContextClassLoader(MyContextTest.class.getClassLoader().getParent());
    ServiceLoader<Driver> loader = ServiceLoader.load(Driver.class); // 拿到数据库连接驱动
    Iterator<Driver> it = loader.iterator();
    while (it.hasNext()) {
        Driver driver = it.next();
        System.out.println("driver: " + driver + ",loader: " + driver.getClass().getClassLoader());
    }
    System.out.println("当前线程上下文类加载器: " + Thread.currentThread().getContextClassLoader());
    System.out.println("service的类加载器: " + ServiceLoader.class.getClassLoader());
}
}

运行结果:
在这里插入图片描述
关于线程上下文类加载器,扩展到Servlet、tomcat和框架搭载相关的知识,有时间一定要去研究研究

接下来是java字节码
字节码文件结构解析

猜你喜欢

转载自blog.csdn.net/weixin_42603009/article/details/89442278