ClassLoader類加載器工做原理分析

前言

最近在看公司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種狀況下執行

  1. 碰到new、getstatic、putstatic、或invoikestatic這個字節碼指令時,若是沒有對類進行初始化則對類進行初始化。對應java代碼經常使用分別爲使用new關鍵字實例化一個對象、讀取或設置一個static類型的變量、調用static類型的方法。
  2. 使用java.lang.reflect包的方法對類進行反射操做的時候,若是沒有進行過初始化,則須要初始化。
  3. 當初始化一個類的時候,若是發現其父類尚未初始化,則會觸發其父類初始化。
  4. 當虛擬機啓動時,用戶須要指定一個要執行的主類,虛擬機會行初始化這個主類。
  5. 當使用JDK7的動態語言支持時,若是一個java.lang.invoke.MethodHandle實例最後的解析結果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,而且這個方法句柄所對應的類沒有初始化,則會先觸發其初始化。

雙親委派模式加載類

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()與 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

相關文章
相關標籤/搜索