ClassLoader&雙親委派&類初始化過程
- 2020 年 3 月 17 日
- 筆記
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 負責執行類中的靜態初始化程式碼、構造器程式碼以及靜態屬性的初始化(目標值)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指定路徑中的類庫,開發者可以直接使用標準擴展類載入器

1 public class Launcher { 2 ...... 3 static class ExtClassLoader extends URLClassLoader { 4 private static volatile Launcher.ExtClassLoader instance; 5 6 public static Launcher.ExtClassLoader getExtClassLoader() throws IOException { 7 if (instance == null) { 8 Class var0 = Launcher.ExtClassLoader.class; 9 synchronized(Launcher.ExtClassLoader.class) { 10 if (instance == null) { 11 instance = createExtClassLoader(); 12 } 13 } 14 } 15 16 return instance; 17 } 18 } 19 ..... 20 } 21
View Code
系統類載入器(應用程式載入器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 方法