【JVM基礎知識】java類加載機制

一、什麼是Classloader

一個Java程序要想運行起來,首先需要經過編譯生成 .class文件,然後創建一個運行環境(jvm)來加載字節碼文件到內存運行,而.class 文件是怎樣被加載中jvm中的就是Java Classloader所做的事情。

那麼.class文件什麼時候會被類加載器加載到jvm中運行呢?比如執行new操作時候,當我們使用Class.forName(“包路徑+類名”)、Class.forName(“包路徑+類名”,initialize,classloader)、ClassLoader.loadclass(“包路徑+類名”);時候就觸發了類加載器去類加載對應的路徑去查找*.class,並創建Class對象。

Class.forName():將類的.class文件加載到jvm中之外,還會對類進行解釋並執行類中的static塊;

ClassLoader.loadClass():只幹一件事情,就是將.class文件加載到jvm中,不會執行static中的內容,只有在newInstance纔會去執行static塊。

:Class.forName(name, initialize, loader)帶參函數也可控制是否加載static塊。並且只有調用了newInstance()方法採用調用構造函數,創建類的對象 。

二、Java自帶的Classloader

如何尋找類加載器先來看個例子:

public class ClassLoaderTest {

    public static void main(String[] args) {
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        System.out.println(loader);// 應用類加載器
        System.out.println(loader.getParent());// 擴展類加載器
        System.out.println(loader.getParent().getParent());
    }

}

運行後,輸出結果:

sun.misc.Launcher$AppClassLoader@56a96eba
sun.misc.Launcher$ExtClassLoader@da4a1c9
null

從上面的結果可以看出,並沒有獲取到ExtClassLoader的父Loader,原因是Bootstrap ClassLoader(引導類加載器)是用C語言實現的,找不到一個確定的返回父Loader的方式,於是就返回null。

這幾種類加載器的層次關係如下圖所示:

注意:這裏父類加載器並不是通過繼承關係來實現的,而是採用組合實現的。一般我們都認爲ExtClassloader的父類加載器是BootStarpClassloader,但是其實他們之間根本是沒有父子關係的,只是在ExtClassloader找不到要加載類時候會去委託BootStrap加載器去加載。

站在Java虛擬機的角度來講,只存在兩種不同的類加載器:

  1. 啓動類加載器:它使用C++實現(這裏僅限於Hotspot,也就是JDK1.5之後默認的虛擬機,有很多其他的虛擬機是用Java語言實現的),是虛擬機自身的一部分;
  2. 所有其他的類加載器:這些類加載器都由Java語言實現,獨立於虛擬機之外,並且全部繼承自抽象類java.lang.ClassLoader,這些類加載器需要由啓動類加載器加載到內存中之後才能去加載其他的類。

站在Java開發人員的角度來看,類加載器可以大致劃分爲以下三類:

2.1 BootstrapClassloader

引導類加載器,又稱啓動類加載器,是最頂層的類加載器,主要用來加載Java核心類,如rt.jar、resources.jar、charsets.jar等,Sun的JVM中,執行java的命令中使用-Xbootclasspath選項或使用- D選項指定sun.boot.class.path系統屬性值可以指定附加的類,它不是 java.lang.ClassLoader的子類,而是由JVM自身實現的該類c 語言實現,Java程序訪問不到該加載器。

通過下面代碼可以查看該加載器加載了哪些jar包:

package test;

import java.net.URL;

public class ClassLoaderTest {

    public static void main(String[] args) {       
        getJars();
    }
    
    public static void getJars() {  
        URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();    
        for (int i = 0; i < urls.length; i++) {    
            System.out.println(urls[i].toExternalForm());    
        }   
    }

}

執行結果:

file:/Z:/JDK/jre/lib/resources.jar
file:/Z:/JDK/jre/lib/rt.jar
file:/Z:/JDK/jre/lib/sunrsasign.jar
file:/Z:/JDK/jre/lib/jsse.jar
file:/Z:/JDK/jre/lib/jce.jar
file:/Z:/JDK/jre/lib/charsets.jar
file:/Z:/JDK/jre/lib/jfr.jar
file:/Z:/JDK/jre/classes

寫到這裏大家應該都知道,我們並沒有在classpath裏面指定這些類的路徑,爲啥還是能被加載到jvm並使用起來了吧,因爲這些是bootstarp來加載的。

2.2 ExtClassloader

擴展類加載器,主要負責加載Java的擴展類庫,默認加載JAVA_HOME/jre/lib/ext/目下的所有jar包或者由java.ext.dirs系統屬性指定的jar包。放入這個目錄下的jar包對所有AppClassloader都是可見的(後面會知道ExtClassloader是AppClassloader的父加載器)。那麼ext都是在哪些地方加載類呢?

System.out.println(System.getProperty("java.ext.dirs"));

2.3 AppClassloader

系統類加載器,又稱應用加載器,本文說的SystemClassloader和APPClassloader是一個東西,它負責在JVM啓動時,加載來自在命令java中的-classpath或者java.class.path系統屬性或者 CLASSPATH操作系統屬性所指定的JAR類包和類路徑。調用ClassLoader.getSystemClassLoader()可以獲取該類加載器。如果沒有特別指定,則用戶自定義的任何類加載器都將該類加載器作爲它的父加載器,這點通過ClassLoader的無參構造函數可以知道如下:

protected ClassLoader() {
   this(checkCreateClassLoader(), getSystemClassLoader());
}

執行以下代碼即可獲得classpath加載路徑:

System.out.println(System.getProperty("java.class.path"));

2.4 類加載器原理

Java類加載器使用的是雙親委派機制,如果一個類加載器收到了類加載的請求,它首先不會自己去嘗試加載這個類,而是把請求委託給父加載器去完成,依次向上,因此,所有的類加載請求最終都應該被傳遞到頂層的啓動類加載器中,只有當父加載器在它的搜索範圍中沒有找到所需的類時,即無法完成該加載,子加載器纔會嘗試自己去加載該類。

1、當AppClassLoader加載一個class時,它首先不會自己去嘗試加載這個類,而是把類加載請求委派給父類加載器ExtClassLoader去完成。

2、當ExtClassLoader加載一個class時,它首先也不會自己去嘗試加載這個類,而是把類加載請求委派給BootStrapClassLoader去完成。

3、如果BootStrapClassLoader加載失敗(例如在$JAVA_HOME/jre/lib裏未查找到該class),會使用ExtClassLoader來嘗試加載;

4、若ExtClassLoader也加載失敗,則會使用AppClassLoader來加載,如果AppClassLoader也加載失敗,則會報出異常ClassNotFoundException。

那麼問題來了,爲啥使用這種方式呢?因爲這樣可以避免重複加載,當父親已經加載了該類的時候,就沒有必要子ClassLoader再加載一次。考慮到安全因素,我們試想一下,如果不使用這種委託模式,那我們就可以隨時使用自定義的String來動態替代java核心api中定義的類型,這樣會存在非常大的安全隱患,而雙親委託的方式,就可以避免這種情況,因爲String已經在啓動時就被引導類加載器(Bootstrcp ClassLoader)加載,所以用戶自定義的ClassLoader永遠也無法加載一個自己寫的String,除非你改變JDK中ClassLoader搜索類的默認算法。

雙親委派模型意義

1、系統類防止內存中出現多份同樣的字節碼;

2、保證Java程序安全穩定運行。

下面我們從源碼看如何實現雙親委派機制:

    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 {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        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;
        }
    }

分析代碼知道:

  1. 首先從jvm緩存查找該類,如果該類之前被加載過,則直接從jvm緩存返回該類,否者看當前類加載器是否有父加載器;
  2. 如果有父加載器的話則委託爲父類加載器進行加載,否者委託BootStrapClassloader進行加載;
  3. 如果還是沒有找到,則調用當前Classloader的findclass方法進行查找。

從上面源碼知道要想修改類加載委託機制,實現自己的載入策略可以通過覆蓋ClassLoader的findClass方法或者覆蓋loadClass方法來實現。

Java應用啓動過程是首先Bootstarp Classloader加載rt.jar包裏面的sun.misc.Launcher類,而該類內部使用BootstarpClassloader加載器構建和初始化Java中三種類加載和線程上下文類加載器,然後在根據不同場景去使用這些類加載器去自己的類查找路徑去加載類。

參考

http://ifeve.com/classloader%e8%a7%a3%e6%83%91/

http://www.cnblogs.com/ityouknow/p/5603287.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章