JVM 類加載機制之ClassLoader&雙親委派深入解讀

1.class sycle

類加載的生命週期:加載(Loading)–>驗證(Verification)–>準備(Preparation)–>解析(Resolution)–>初始化(Initialization)–>使用(Using)–>卸載(Unloading)
在這裏插入圖片描述
關注點1: loading 將class 二進制文件加載到內存中

  • 通過一個類的全限定名來獲取定義此類的二進制字節流。
  • 將這個字節流所代表的靜態存儲結構轉化爲方法區的運行時數據結構。
  • 在java堆中生成一個代表這個類的java.lang.Class對象,做爲方法區這些數據的訪問入口。

加載階段完成之後二進制字節流就按照虛擬機所需的格式存儲在方區去中。
關注點2: verifaction 這一階段的目的是爲了確保Class文件的字節流中包含的信息符合當前虛擬機的要求

  • 文件格式驗證:驗證字節流是否符合Class文件格式的規範,並且能被當前版本的虛擬機處理
  • 元數據驗證:對字節碼描述的信息進行語義分析,以確保其描述的信息符合java語言規範的要求。
  • 字節碼驗證:這個階段的主要工作是進行數據流和控制流的分析。任務是確保被驗證類的方法在運行時不會做出危害虛擬機安全的行爲。
  • 符號引用驗證:這一階段發生在虛擬機將符號引用轉換爲直接引用的時候(解析階段),主要是對類自身以外的信息進行匹配性的校驗。目的是確保解析動作能夠正常執行。

關注點3: preparation 對靜態變量賦默認值,而不是初始值(目標指),準備階段是正式爲靜態變量分配內存並設置初始值,這些內存都將在方法區中進行分配,這裏的變量僅包括類變量(靜態變量)不包括實例(成員)變量。
關注點4: resolution :解析是虛擬機將常量池的符號引用替換爲直接引用的過程

  • 符號引用:符號引用以一組符號來描述所引用的目標,符號可以是任意形式的字面量,只要使用時能無歧義地定位到目標即可。符號引用與虛擬機實現的內存佈局無關,引用的目標並不一定已經加載到內存中。
  • 直接引用:直接引用可以是直接指向目標的指針,相對偏移量或是一個能間接定位到目標的句柄。直接飲用是與內存佈局相關的。
  • 類或接口的解析
  • 字段的解析
  • 類方法解析
  • 接口方法解析

關注點5: initializing 負責執行類中的靜態初始化代碼、構造器代碼以及靜態屬性的初始化(目標值)

  • 遇到new、getstatic、putstatic、invokestatic這4個字節碼指令時,如果類沒有進行過初始化,出發初始化操作。 訪問final 變量除外 ??
  • 使用java.lang.reflect包的方法對類進行反射調用時。
  • 當初始化一個類的時候,如果發現其父類還沒有執行初始化則進行初始化。
  • 虛擬機啓動時用戶需要指定一個需要執行的主類,虛擬機首先初始化這個主類。
  • 動態語言支持java,lang.invoke.MethodHandle解析結果爲REF_getstatic REF_invokestatic的方法句柄時,該類必須初始化。
    注意:接口與類的初始化規則在第三點不同,接口不要氣所有的父接口都進行初始化。

2 不同類加載器說明

引導類加載器(BootStrap) :
主要負責加載JVM自身需要的類,該加載器由C++實現,加載的是<JAVA_HOME>/lib 下的class文件,或者 -Xbootclasspath 參數指定的路徑下的jar包,注意必須由虛擬機按照文件名識別加載jar包,如rt.jar,如果文件名不被虛擬機識別,即使把jar丟到lib目錄下也是沒有最用的(出於考慮,Bootstrap 啓動類加載器只加載java、javax、sun開頭的類),引導類加載器在hotspot 虛擬中使用C++語言實現,它是虛擬機的一部分。除了引導類加載器之外,其他類加載器都是由Java語言實現,並且全部集成自java.lang.ClassLoader,他們是獨立於虛擬機外部的。

擴展類加載器(Extension) :
擴展類加載是指Sun公司實現的類,它是由Sun的ExtClassLoader實現的,是Lancher類的靜態內部類。他負責加載<JAVA_HOME>/lib/ext目錄下或有系統變量-Djava.ext.dir指定路徑中的類庫,開發者可以直接使用標準擴展類加載器

public class Launcher {
  ......
static class ExtClassLoader extends URLClassLoader {
        private static volatile Launcher.ExtClassLoader instance;

        public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {
            if (instance == null) {
                Class var0 = Launcher.ExtClassLoader.class;
                synchronized(Launcher.ExtClassLoader.class) {
                    if (instance == null) {
                        instance = createExtClassLoader();
                    }
                }
            }

            return instance;
        }
}
  .....
}

系統類加載器(應用程序加載器AppClassLoader):
它是由Sun的AppClassLoader實現的,它負責加載系統路徑 java -classpath 或者-D java.class.path指定路徑下的類庫,也就是我們經常使用到的classpath路徑,開發者直接使用系統類的加載器,一般情況下該類加載器是程序組中默認的類加載器,通過ClassLoad.getSystemClassLoader()方法可以獲取到該類的加載器。

自定義類加載器(Custom ClassLoader ):
在程序運行期間, 通過java.lang.ClassLoader的子類動態加載class文件, 體現java動態實時類裝入特性

3.ClassLoader加載類過程(雙親委派)

JVM在加載類時默認採用的雙親委派機制。通俗講,就是某個特定的類加載器在接到類加載器的請求時,受限將加載任務委傳給父類加載器, 依次遞歸,如果父類加載器可以完成類的加載任務,就返回成功;只有父類加載器無法完成此加載器任務時,纔去自己加載。
雙親委派過程

4.ClassLoader加載類過程(雙親委派流程圖)

雙親委派類加載流程圖

5.爲什麼需要雙親委派機制?

爲了系統類的安全,類似“java.lang.Object”這種核心類,JVM需要保證他們生成的對象都會被認定爲同一類型 ,如果用戶編寫了一個lava.lang.Object的同名類並放在ClassPath中,多個類加載器都去加載這個類到內存中,系統中會出現多個不同的Object類,那麼類之間的比較傲結果以及唯一性將無法保證,並且如果不使用這種雙親委派模型將會給虛擬機的安全帶來安全隱患。所以要讓類對象進行比較有意義,前提是他們要被同一個類加載器加載。即“通過代理模式,對於java核心類庫的類的加載工作由引導類加載器統一完成,保證了Java應用所使用的都是同一個版本的Java 核心庫的類,是相互兼容的”

好處是防止內存中出現多份相同的字節碼。

6.能不能自己寫個類叫java.lang.System?

答案:通常不可以,但可以採取另類方法達到這個需求。

解釋:爲了不讓我們寫System類,類加載採用委託機制,這樣可以保證爸爸們優先,爸爸們能找到的類,兒子就沒有機會加載。而System類是Bootstrap加載器加載的,就算自己重寫,也總是使用Java系統提供的System,自己寫的System類根本沒有機會得到加載。

但是,我們可以自己定義一個類加載器來達到這個目的,爲了避免雙親委託機制,這個類加載器也必須是特殊的。由於系統自帶的三個類加載器都加載特定目錄下的類,如果我們自己的類加載器加載一個特殊的目錄,那麼系統的加載器就無法加載,也就是最終還是由我們自己的加載器加載。

7.如何自定義類加載器

  • 繼承 ClassLoader
  • overwrite findClass()

8.如何打破雙親委派?

  • 集成ClassLoader
  • 重寫loadClass 方法

9.例子

   System.out.println("1 當前類的類加載器是 app :"+T002_ClassLoaderLevel.class.getClassLoader());
   System.out.println("2 app 類加載器是 null :"+T002_ClassLoaderLevel.class.getClassLoader().getClass().getClassLoader());
   System.out.println("2 app、 加載器的 parent 是 ext :"+T002_ClassLoaderLevel.class.getClassLoader().getParent());
   System.out.println("3. ext的classloader 是null:"+T002_ClassLoaderLevel.class.getClassLoader().getParent().getClass().getClassLoader());
   System.out.println("4. ext的classloader 是null :"+T002_ClassLoaderLevel.class.getClassLoader().getParent().getParent());
   System.out.println("5.ext 的 classLoader 是 null:"+T002_ClassLoaderLevel.class.getClassLoader().getParent().getClass().getClassLoader());;
   System.out.println("6. app 的classloader 是null :"+T002_ClassLoaderLevel.class.getClassLoader().getClass().getClassLoader());;

[參考文獻]:
1.《深入理解JVM》
2. https://www.cnblogs.com/developer-ios/p/5550789.html

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