ClassLoader原理分析

來源:互聯網
上載者:User

標籤:

前文:Java中的所有類,必須被裝載到jvm中才能運行,這個裝載工作是由jvm中的類裝載器完成的。類裝載器所做的工作實質是把類檔案從硬碟讀取到jvm運行記憶體中,或者從網路中讀取到jvm運行記憶體中JVM在載入類的時候,都是通過ClassLoader的loadClass()方法來載入class的。 例如:
public class TestClassLoader {    public static void main(String[] args) {        System.out.println(TestClassLoader.class.getClassLoader());    }}

 運行結果:

[email protected]

 

但是ClassLoader是怎樣載入class的呢?

 

一、java ClassLoader體系

Java預設是有三個ClassLoader,按層次關係從上到下依次是:

  • Bootstrap ClassLoader
  • ExtClassLoader
  • AppClassLoader
  Bootstrap ClassLoader是最頂層的ClassLoader,比較特殊,是用C++編寫整合在JVM中的,是JVM啟動的時候用來載入一些核心類的,比如:rt.jar,resources.jar,charsets.jar,jce.jar等 
import java.net.URL;public class TestClassLoader {    public static void main(String[] args) {        URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();        for (int i = 0; i < urls.length; i++) {              System.out.println(urls[i].toExternalForm());          }    }}

運行結果為:

 

file:/C:/Program%20Files/Java/jre1.8.0_73/lib/resources.jarfile:/C:/Program%20Files/Java/jre1.8.0_73/lib/rt.jarfile:/C:/Program%20Files/Java/jre1.8.0_73/lib/sunrsasign.jarfile:/C:/Program%20Files/Java/jre1.8.0_73/lib/jsse.jarfile:/C:/Program%20Files/Java/jre1.8.0_73/lib/jce.jarfile:/C:/Program%20Files/Java/jre1.8.0_73/lib/charsets.jarfile:/C:/Program%20Files/Java/jre1.8.0_73/lib/jfr.jarfile:/C:/Program%20Files/Java/jre1.8.0_73/classes

 

 

Bootstrap ClassLoader載入的類全都是java自有的核心類。

ExtClassLoader、AppClassLoader都是繼承自Bootstrap ClassLoader。

 

ExtClassLoader是用來載入擴充類載入器,負責載入Java的擴充類庫,預設載入JAVA_HOME/jre/lib/ext/目錄下的所有的jar

 

AppClassLoader叫做系統類別載入器,負責載入應用程式classpath目錄下的所有jar和class檔案,包括我們平時運行jar包指定cp參數下的jar包

 我們問題是,雖然有三個類載入器,但是jvm怎樣知道一個java 類是哪個ClassLoader來載入的呢,使用什麼規則或者演算法?  二、ClassLoader雙親委派模式通過查看java.lang.ClassLoader(所有的類載入器都是繼承該類)的public Class<?> loadClass(String name) throws ClassNotFoundException ;又調用了protected Class<?> loadClass(String name, boolean resolve)        throws ClassNotFoundException; 
 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);            if (c == null) {                long t0 = System.nanoTime();                try {                    //如果父載入器不為空白,使用父載入器載入class                    if (parent != null) {                        c = parent.loadClass(name, false);                    } else {                        //如果父載入器也為空白,使用bootstarpClassLoader載入                        c = findBootstrapClassOrNull(name);                    }                } 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);                    // 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);            }            return c;        }    }

 

這時問題是,parent是什麼?

public abstract class ClassLoader {    // The parent class loader for delegation    private final ClassLoader parent;        ...}

 

 

原來ClassLoader內部使用了父載入器,這就是雙親委託模式。

:

 當前類載入器先不載入class,委託給父載入器載入class,如果父載入器沒有載入成功,本類載入器再載入class。 可以使用下面代碼測試下:
public class TestClassLoader {    public static void main(String[] args) {        ClassLoader loader = MyAppClassLoader.class.getClassLoader();        while(loader != null) {            System.out.println(loader);            loader = loader.getParent();           }        System.out.println(loader);    }}

 運行結果為:

[email protected][email protected]null

 

 

這樣做的好處是:為了安全性,避免使用者自己編寫的類動態替換Java的一些核心類,比如String,同時也避免了重複載入,因為JVM中區分不同類,不僅僅是根據類名,相同的class檔案被不同的ClassLoader載入就是不同的兩個類,如果相互轉型的話會拋java.lang.ClassCaseException 三、自訂ClassLoader通常我們不需要自己實現ClassLoader,但是在一些特殊的情境需要自訂ClassLoader,比如tomcat載入webApp下面的Class,或者一些RPC架構,我們需要繼承java.lang.ClassLoader ,比如:
public class MyAppClassLoader extends ClassLoader{    @Override    public Class<?> findClass(String name) throws ClassNotFoundException {        //從特殊路徑下讀取class,或者從網路中讀取class,不管咋樣,實現自己的方法,返回載入class                //...    }}

 

 四、ContextLoader當前classLoader

首先關於類的載入補充一點就是如果類A是被一個載入器載入的,那麼類A中引用的B也是由這個載入器載入的(如果B還沒有被載入的話),通常情況下就是類B必須在類A的classpath下。

但是考慮多線程環境下不同的對象可能是由不同的ClassLoader載入的,那麼當一個由ClassLoaderC載入的對象A從一個線程被傳到另一個線程ThreadB中,而ThreadB是由ClassLoaderD載入的,這時候如果A想擷取除了自己的classpath以外的資源的話,它就可以通過Thread.currentThread().getContextClassLoader()來擷取線程內容相關的ClassLoader了,一般就是ClassLoaderD了,可以通過Thread.currentThread().setContextClassLoader(ClassLoader)來顯示的設定

之所以有Context ClassLoader是因為Java的這種“雙親委託”機制是有局限性的:

比如: JNDI為例,JNDI的類是由bootstrap ClassLoader從rt.jar中間載入的,但是JNDI具體的核心驅動是由正式的實現提供的,並且通常會處於-cp參數之下(註:也就是預設的System ClassLoader管理),這就要求bootstartp ClassLoader去載入只有SystemClassLoader可見的類,正常的邏輯就沒辦法處理。怎麼辦呢?parent可以通過獲得當前調用Thread的方法獲得調用線程的>Context ClassLoder 來載入類。

 

TODO ,contextLoader會繼續補充,待完善。

 

ClassLoader原理分析

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.