jvm原理(23)线程上下文类加载器实战分析与难点剖析
Posted 魔鬼_
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了jvm原理(23)线程上下文类加载器实战分析与难点剖析相关的知识,希望对你有一定的参考价值。
我们紧接着上一个例子的代码进行:
public class MyTest26
public static void main(String[] args)
ServiceLoader<Driver> serviceLoader = ServiceLoader.load(Driver.class);
Iterator<Driver> iterator = serviceLoader.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());
首先MyTest26的类加载器是系统类加载器,
程序运行到【ServiceLoader serviceLoader = ServiceLoader.load(Driver.class);】的时候回去加载ServiceLoader,系统类加载器会根据双亲委托模型往上委派,直到最后ServiceLoader被启动类加载器加载,那么我们看一下ServiceLoader.load()方法的实现:
public static <S> ServiceLoader<S> load(Class<S> service)
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
既然ServiceLoader是由启动类加载器加载,那么ServiceLoader里边的类都会用启动类加载器去加载,但是呢我们的mysql驱动不在启动类加载器加载的目录下边,我们的mysql驱动位于classpath下边,无法用启动类加载器加载,这个时候,我们可以看到load方法使用了线程上下文加载器,线程上下文加载器默认是系统类加载器上一节已经介绍过,所以load方法得到的ClassLoader cl是AppClassLoader,然后将cl传递给ServiceLoader.load(service, cl)方法:
//加载器的缓存
// Cached providers, in instantiation order
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
public static <S> ServiceLoader<S> load(Class<S> service,ClassLoader loader)
//调用ServiceLoader构造器
return new ServiceLoader<>(service, loader);
//ServiceLoader构造器
private ServiceLoader(Class<S> svc, ClassLoader cl)
//判空
service = Objects.requireNonNull(svc, "Service interface cannot be null");
//加载器判空,空的情况是使用系统类加载器
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
//安全相关的
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
//调用reload,刷新缓存
reload();
//刷新缓存
public void reload()
//清空缓存
providers.clear();
//LazyIterator是ServiceLoader的私有内部类
lookupIterator = new LazyIterator(service, loader);
//LazyIterator 部分代码
// Private inner class implementing fully-lazy provider lookup
//用来实现懒加载方式 的提供者的查找的私有内部类
private class LazyIterator implements Iterator<S>
Class<S> service;
ClassLoader loader;
Enumeration<URL> configs = null;
Iterator<String> pending = null;
String nextName = null;
private LazyIterator(Class<S> service, ClassLoader loader)
this.service = service;
this.loader = loader;
private boolean hasNextService()
....
private S nextService()
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class<?> c = null;
try
//cn相当于java.sql.Drive文件下的某一行的全限定名,false表示不初始化,loader加载器是我们传递进来的线程上下文类加载器
//(即,系统类加载器或者应用类加载器)
//Class.forName()方法见之前的介绍:https://blog.csdn.net/wzq6578702/article/details/79950220
c = Class.forName(cn, false, loader);
catch (ClassNotFoundException x)
fail(service,
"Provider " + cn + " not found");
if (!service.isAssignableFrom(c))
fail(service,
"Provider " + cn + " not a subtype");
try
S p = service.cast(c.newInstance());
providers.put(cn, p);
return p;
catch (Throwable x)
fail(service,
"Provider " + cn + " could not be instantiated",
x);
throw new Error(); // This cannot happen
public boolean hasNext()
...
public S next()
...
到此为止我们看到的程序的打印结果
driver: class com.mysql.jdbc.Driverloader: sun.misc.Launcher$AppClassLoader@18b4aac2
driver: class com.mysql.fabric.jdbc.FabricMySQLDriverloader: sun.misc.Launcher$AppClassLoader@18b4aac2
当前线程上下文类加载器: sun.misc.Launcher$AppClassLoader@18b4aac2
ServiceLoader的类加载器: null
前2行driver就是java.sql.Drive文件里边的2行,而上下文类加载器是默认的系统类加载器,ServiceLoader属于rt.jar是由启动类加载器加载。
接下来改一下程序成如下:
public class MyTest26
public static void main(String[] args)
//添加这行代码
Thread.currentThread().setContextClassLoader(MyTest26.class.getClassLoader().getParent());
ServiceLoader<Driver> serviceLoader = ServiceLoader.load(Driver.class);
Iterator<Driver> iterator = serviceLoader.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());
打印结果:
当前线程上下文类加载器: sun.misc.Launcher$ExtClassLoader@14ae5a5
ServiceLoader的类加载器: null
可以看到循环没有去执行,上下文类加载器是扩展类加载器没啥问题,因为系统类加载器的上级是扩展类加载器,但是为什么循环是空的呢?原因就是扩展类加载器无法加载classpath下边的类,mysql的jar包是放在classpath下边的。
其实加上-XX:+TraceClassLoading启动参数,我们可以显式的看到驱动被加载。
以上是关于jvm原理(23)线程上下文类加载器实战分析与难点剖析的主要内容,如果未能解决你的问题,请参考以下文章
jvm原理(22)线程上下文类加载器本质剖析与实做&ServiceLoader在SPI中的重要作用分析
《Java虚拟机原理图解》5. JVM类加载器机制与类加载过程
jvm原理(24)通过JDBC驱动加载深刻理解线程上下文类加载器机制