最近在看公司RPC框架Pegion的源码,里面不少地方用到了Thread.currentThread().getContextClassLoader()。不太清楚Thread.currentThread().getContextClassLoader()与 Class.getClassLoader()二者获取到的ClassLoader的区别。而后便有了本文。html
简单的讲就是一个专门负责把咱们写的java代码编译后生成的字节码(.class文件)加载到JVM的内存中的类(ClassLoad)。哪问题来了ClassLoad自己对应的类又由谁加载,答案是BootstrapClassLoader。其被称做启动类加载器,是最顶层的类加载器,主要用来加载Java核心类,如rt.jar、resources.jar、charsets.jar等。它不是 java.lang.ClassLoader的子类,而是由JVM自身实现的该类c 语言实现,Java程序访问不到该加载器。程序启动时JVM会利用该加载器加载rt.jar、resources.jar、charsets.jar等文件。java
经过一下例子看一下类载器之间的关系。mysql
public class App { public static void main( String[] args ) { ClassLoader loader = App.class.getClassLoader(); while ( null != loader) { System.out.println(loader.getClass().getName()); loader = loader.getParent(); } } }
结果:sql
sun.misc.Launcher$AppClassLoader sun.misc.Launcher$ExtClassLoader
能够看到App的父类加载器为AppClassLoader,AppClassLoader的父类加载器为ExtClassLoader,ExtClassLoader的父类加载器为null。实际是ExtClassLoader类是由BootstrapClassLoader加载的,全部能够把BootstrapClassLoader做为ExtClassLoader的父类加载器。bootstrap
ExtClassLoader 扩展类加载器,主要负责加载Java的扩展类库,默认加载JAVA_HOME/jre/lib/ext/目下的全部jar包或者由java.ext.dirs系统属性指定的jar包。数据结构
AppClassLoader 系统类加载器,又称应用加载器,它负责在JVM启动时,加载来自在命令java中的-classpath或者java.class.path系统属性或者 CLASSPATH操做系统属性所指定的JAR类包和类路径。mybatis
ExtClassLoader与AppClassLoader类加载器的构建在sun.misc.Launcher类中完成。app
public Launcher(){ ExtClassLoader localExtClassLoader; try { //首先建立了ExtClassLoader localExtClassLoader = ExtClassLoader.getExtClassLoader(); } catch (IOException localIOException1) { throw new InternalError("Could not create extension class loader"); } try { //而后以ExtClassloader做为父加载器建立了AppClassLoader this.loader = AppClassLoader.getAppClassLoader(localExtClassLoader); } catch (IOException localIOException2) { throw new InternalError("Could not create application class loader"); } //这个是个特殊的加载器后面会讲到,这里只须要知道默认下线程上下文加载器为appclassloader Thread.currentThread().setContextClassLoader(this.loader); ................ }
这个关系指的是对应的类加载器是由谁加载过来的,或者理解为经过ClassLoader.getParent方法获得,但于BootstrapClassLoader,java程序没法访问返回的为null。框架
JVM何时触发类的加载。在这以前先简单的看一下类的生生命同期,毕竟加载类是为了使用这个类。less
类的生命周期有以下几个阶段,加载、链接(验证、准备、解析)、初始化、使用、卸载,其中加载、验证、准备、初始化和卸载这5个阶段的顺序是肯定的,类的加载过程必须按这个顺序来,而解析的阶段则不必定:它在某些状况下能够在初始化化阶段以后再开始,这是为支持java语言的运行时绑定。什么状况下须要开始第一阶段中类加载。java虚拟机规范中并无强制要求,具体的JVM实现能够不同。但对于初始化阶段,虚拟机规范则严格规定了只有5种状况必须当即对类进行初始化,而这个以前必定要完成加载、验证、准备。初始化阶段会在如下5种状况下执行
java语言中类的加载采用双亲委派模式,即一个在加载一个类的过程当中当前的类加载器,会先尝试让其父类加载器进行加载,若是父类加载器没法加载到,再尝试由当前的类加载器进行加载。ClassLoader.loadClass方法
/** * Loads the class with the specified <a href="#name">binary name</a>. The * default implementation of this method searches for classes in the * following order: * * <ol> * * <li><p> Invoke {@link #findLoadedClass(String)} to check if the class * has already been loaded. </p></li> * * <li><p> Invoke the {@link #loadClass(String) <tt>loadClass</tt>} method * on the parent class loader. If the parent is <tt>null</tt> the class * loader built-in to the virtual machine is used, instead. </p></li> * * <li><p> Invoke the {@link #findClass(String)} method to find the * class. </p></li> * * </ol> * * <p> If the class was found using the above steps, and the * <tt>resolve</tt> flag is true, this method will then invoke the {@link * #resolveClass(Class)} method on the resulting <tt>Class</tt> object. * * <p> Subclasses of <tt>ClassLoader</tt> are encouraged to override {@link * #findClass(String)}, rather than this method. </p> * * <p> Unless overridden, this method synchronizes on the result of * {@link #getClassLoadingLock <tt>getClassLoadingLock</tt>} method * during the entire class loading process. * * @param name * The <a href="#name">binary name</a> of the class * * @param resolve * If <tt>true</tt> then resolve the class * * @return The resulting <tt>Class</tt> object * * @throws ClassNotFoundException * If the class could not be found */ protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded Class<?> c = findLoadedClass(name);//(1) if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) { c = parent.loadClass(name, false);//(2) } else { c = findBootstrapClassOrNull(name);//(3) } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); c = findClass(name);//(4) // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c);//(5) } return c; } }
其中(1)处判断对应类是否已加载过,加载过直接返回,(2)处调用父类加载器进行加载,(3)当没有父类加载器时调用Bootstrap类加载加载,(4)处调用当前类加载器本身定义的加载方法加载类,默认抛出异常,(5)resolveClass最终调用了一个本地方法作link,这里的link主要作了,验证Class以确保类装载器格式和行为正确;准备后续步骤所需的数据结构;解析所引用的其余类。通常状况下要息定义类加载器通常只要继承ClassLoader,而后实现他的findClass方法。
介绍完了前面那么多,看看Thread.currentThread().getContextClassLoader()与 Class.getClassLoader()的区别。有些场景下面要咱们要打破双亲委派加载的模式,那么这个时候该如何处理呢?答案就是利用Thread.currentThread().getContextClassLoader()。
先看下源码中Thread.currentThread().getContextClassLoader()与 Class.getClassLoader()返回的是什么?
Thread.currentThread().getContextClassLoader()
/** * Returns the context ClassLoader for this Thread. The context * ClassLoader is provided by the creator of the thread for use * by code running in this thread when loading classes and resources. * If not {@linkplain #setContextClassLoader set}, the default is the * ClassLoader context of the parent Thread. The context ClassLoader of the * primordial thread is typically set to the class loader used to load the * application. * * <p>If a security manager is present, and the invoker's class loader is not * {@code null} and is not the same as or an ancestor of the context class * loader, then this method invokes the security manager's {@link * SecurityManager#checkPermission(java.security.Permission) checkPermission} * method with a {@link RuntimePermission RuntimePermission}{@code * ("getClassLoader")} permission to verify that retrieval of the context * class loader is permitted. * * @return the context ClassLoader for this Thread, or {@code null} * indicating the system class loader (or, failing that, the * bootstrap class loader) * * @throws SecurityException * if the current thread cannot get the context ClassLoader * * @since 1.2 */ @CallerSensitive public ClassLoader getContextClassLoader() { if (contextClassLoader == null) return null; SecurityManager sm = System.getSecurityManager(); if (sm != null) { ClassLoader.checkClassLoaderPermission(contextClassLoader, Reflection.getCallerClass()); } return contextClassLoader; }
返回的是Thread的一个成员变量,没有设置时返回的是父类的装载器。
/* The context ClassLoader for this thread */ private ClassLoader contextClassLoader;
Class.getClassLoader()
/** * Returns the class loader for the class. Some implementations may use * null to represent the bootstrap class loader. This method will return * null in such implementations if this class was loaded by the bootstrap * class loader. * * <p> If a security manager is present, and the caller's class loader is * not null and the caller's class loader is not the same as or an ancestor of * the class loader for the class whose class loader is requested, then * this method calls the security manager's {@code checkPermission} * method with a {@code RuntimePermission("getClassLoader")} * permission to ensure it's ok to access the class loader for the class. * * <p>If this object * represents a primitive type or void, null is returned. * * @return the class loader that loaded the class or interface * represented by this object. * @throws SecurityException * if a security manager exists and its * {@code checkPermission} method denies * access to the class loader for the class. * @see java.lang.ClassLoader * @see SecurityManager#checkPermission * @see java.lang.RuntimePermission */ @CallerSensitive public ClassLoader getClassLoader() { ClassLoader cl = getClassLoader0(); if (cl == null) return null; SecurityManager sm = System.getSecurityManager(); if (sm != null) { ClassLoader.checkClassLoaderPermission(cl, Reflection.getCallerClass()); } return cl; }
返回的是加载这个类的加载器。
Java 规定,类依赖的类也由同一个 ClassLoader 加载,结合双亲委派的加载模式,有些场景则要违反这一规则来达成扩展性。例如 jdk 核心包里的 SPI (Service Provider Interface 服务提供接口)机制 ServiceLoader。按双亲委派的原则 ServiceLoader 方法引用的类也须要由 BootstrapClassLoader 来加载,但事实上确并不是如此。这其中,主要是经过线程 Thread 的 ContextClassLoader 来实现的。这里以 MySql 的 JDBC 驱动为例,Driver 是 JDK 提供的接口,mysql 提供的驱动对应的是服务供应商。提供者只需在 JDBC 实现的 Jar 的 META-INF/services/java.sql.Driver 文件里指定实现类的方式暴露驱动提供者。
示例代码:
package com.yeyi.coremybatis; import java.sql.Driver; import java.util.ServiceLoader; /** * @author yeweigen */ public class ServiceLoaderTest { public static void main(String[] args) { //Thread.currentThread().setContextClassLoader(ServiceLoaderTest.class.getClassLoader().getParent()); ServiceLoader<Driver> drivers = ServiceLoader.load(Driver.class); for (Driver driver : drivers) { System.out.println("driver class:" + driver.getClass().getName() +" || loader:" + driver.getClass().getClassLoader()); } System.out.println(ServiceLoaderTest.class.getClassLoader()); System.out.println(Thread.currentThread().getContextClassLoader()); System.out.println(ServiceLoader.class.getClassLoader()); } }
结果:
driver class:com.mysql.jdbc.Driver || loader:sun.misc.Launcher$AppClassLoader@18b4aac2 sun.misc.Launcher$AppClassLoader@18b4aac2 sun.misc.Launcher$AppClassLoader@18b4aac2 null
运行程序能够看到,ServiceLoader 的类加载器为 null 也就是 BootstrapClassLoader。Thread.currentThread().getContextClassLoader() 代指当前线程的上下文类加载器,默认的就是和当前类同样的 AppClassLoader。for 循环里面打印的也是 AppClassLoader。这里说明使用 BootstrapClassLoader 类加载器加载的 ServiceLoader 类加载了使用 AppClassLoader 类加载器的用户类。
public static <S> ServiceLoader<S> load(Class<S> service) { ClassLoader cl = Thread.currentThread().getContextClassLoader(); return ServiceLoader.load(service, cl); }
这里ServiceLoader.load(MyClass.class)换成这个会更好理解一点,Java 规定,类依赖的类也由同一个 ClassLoader 加载,ServiceLoader类由BootstrapClassLoader加载,那么其依赖的MyClass类,也应该由BootstrapClassLoader加载,但按双亲委派模式其应该由AppClassLoader加载。因此在ServiceLoader.load方法中,先经过ClassLoader cl = Thread.currentThread().getContextClassLoader()获得上下文加载器,从以前Launcher的代码中能够知道他就是AppClassLoader。而后ServiceLoader.load(service, cl),指定了加载MyClass是由AppClassLoader完成的不是由BootstrapClassLoader完成的。一句话总结,当咱们想打破类的双亲委派加载模式时,可先Thread.currentThread().setContextClassLoader(),而后Thread.currentThread().getContextClassLoader(),用指定的类加载器加载。
http://www.importnew.com/24381.html
https://www.jianshu.com/p/f151cedd8f77