JVM的類加載
- 1 類加載程序
- 1.1 加載
- 1.2 鏈接
- 1.2.1 驗證
- 1.2.2 準備
- 1.2.3 決議
- 1.3 初始化
- 2 類加載器
- 3 雙親委派機制
- 4 OSGI
1 類加載程序
想要使用一個類,首先需要將其加載到JVM中,類加載到JVM需要經過三個步驟:加載->鏈接->初始化,其中鏈接又分為驗證,準備,決議三步,

1.1 加載
類加載階段會在記憶體中生成一個代表這個類的java.lang.Class物件,作為方法區這個類的各個資料的入口,注意相關資訊不是一定要從Class檔案中獲取,它即可與從ZIP中讀取(如從Jar包,War包中讀取),也可以在運算時生成(動態代理),也可以由其它檔案生成(如將JSP檔案轉換成對應的Class類),
1.2 鏈接
鏈接程序分為驗證,準備,決議三步,
1.2.1 驗證
JAVA是一種相對安全的語言,驗證的意義是為了確保Class檔案的位元組流中包含的資訊符合當前虛擬機要求,不會危害到虛擬機的安全,驗證主要包含檔案格式驗證,元資料驗證,位元組碼驗證,符號參考驗證,
- 檔案格式驗證:驗證位元組流是否符合Class檔案格式規范,并且能被當前JVM加載處理,如常量型別是否支持,
- 元資料驗證:位元組碼資訊進行語言分析,分析是否符合java語言規范,
- 位元組碼驗證:最重要的驗證環節,元資料驗證后對方法體驗證,保證類方法在運行時不會又危害發生,
- 符號參考驗證:驗證符號參考,保證能夠訪問到,不會出現無法訪問的情況,
1.2.2 準備
準備階段正式對類變數分配記憶體,并設定初始默認值,如定義 private static int s = 2020;經過此階段s=0,而不是2020.但是如果定義為private final static int s = 2020,在準備階段會直接將s設定成2020而不是初始值0;
1.2.3 決議
決議階段JVM將常量池中的符號參考替換為直接參考,準備階段只是分配了記憶體,但是類變數并沒有指向那一塊記憶體,這一步就是完成實際指向的作業,
1.3 初始化
初始化階段為類變數設定正確的初始值,如上文 private static int s = 2020中將s賦值2020的動作遍在這一步完成,同時初始化階段也會執行靜態代碼塊,如果有超類,則先對超類執行初始化,
初始化階段是執行類構造器<client>方法的程序,<client>方法是編譯器自動收集類中的類變數賦值操作和靜態陳述句塊中的陳述句合并而成的,JVM會保證父類的<client>方法會在子類的<client>方法執行之前執行完畢,如果這個類沒有靜態變數賦值和靜態代碼塊,那么編譯器可以不為這個類生成<client>方法,
以下幾種情況不會觸發初始化:
- 子類參考父類的靜態變數,只會觸發父類的初始化,不會觸發子類的初始化
- 定義物件陣列,不會觸發初始化
- 常量在編譯程序中直接存入呼叫類的常量池中,本質上并沒有直接參考定義常量的類,不會觸發定義常量所在的類的初始化,如A參考B類的常量final static x = 3,此時不會對B進行初始化,
- 通過類名獲取Class物件,不會觸發類的初始化,
- 通過Class.forName加載指定類時,如果initalize為false,不會觸發初始化,
- 通過ClassLoader默認的loadClss方法,也不會觸發初始化,
2 類加載器
類加載的動作由類加載器完成,對于JVM來說,類的唯一性是通過類的全限定名+類加載器來區分的,不同的類加載器加載的同一個類并不被認為是同一類,如有一個類C,分別用類加載器CL1,CL2加載,一個引數需要CL1的C實體,傳入的確實CL2的C實體,就會報錯java.lang.ClassCastException:C cannot be cast to C,
JVM提供了三種類加載器:啟動類加載器(Bootstrap ClassLoader),擴展類加載器(Extension ClassLoader),應用程式類加載器(Application ClassLoader).
- 啟動類加載器:用來加載Java的核心庫,主要加載的是JVM自身所需要的類,使用C++實作,并非繼承于java.lang.ClassLoader,是JVM的一部分,負責加載JAVA_HOME\lib目錄中的,或者-Xbootclasspath引數指定的路徑中的,且被虛擬機認可[注1]的類,開發者無法直接獲取到其參考,
注1:JVM是按檔案名識別的,如rt.jar,如果檔案名不被虛擬機識別,即使把jar包放在lib目錄下也沒有作用,同時啟動加載器只加載包名為java,javax,sun等開頭的類,且java是特殊包名,開發者的包名不能以java開頭,如果自定義了一個java.***包來讓類加載器加載,那么就會拋出例外java.lang.SecurityException: Prohibited package name: java.*** - 擴展類加載器: 用來加載Java的擴展庫,負責加載JAVA_HOME\lib\ext目錄中的,或通過系統變數java.ext.dirs指定路徑中的類別庫,由java語言實作,開發者可以直接使用,
- 應用程式類加載器:負責加載用戶路徑(classpath)上的類別庫,開發者可以直接使用,可以通過ClassLoader.getSystemClassLoader()獲得,一般情況下程式的默認類加載器就是該加載器,
除了提供的加載器外,開發者可以通過繼承ClassLoader類的方式實作自己的類加載器,

3 雙親委派機制
JVM的類加載機制為雙親委派機制,除了頂層的啟動類加載器,雙親委派機制要求每一個類加載器都要有自己的父加載器,這個父加載器并不是指繼承,而是一種委派關系,雙親委派機制下一個類加載器收到類加載請求不會直接自己去加載,而是先把這個請求委托給自己的父類加載器去執行,如果父類加載器還存在父類加載器,則繼續委托,一直委托到最頂層的啟動類加載器,父加載器可以加載目標類的話就由父加載器完成加載任務,如果父加載器無法完成則子加載器自己嘗試加載,這就是雙親委派機制,
雙親委派機制的優勢:雙親委派機制使得java類隨著他的類加載器具備了一種帶有帶有優先級的層級關系,通過這種層級關系可以避免類的的重復加載,當父加載器已經加載過目標類時,子加載器無需重復加載一次,
其次是安全方面,通過雙親委派機制可以避免核心類不會被隨意替換,例如網路傳遞一個名為java.lang.Integer的類,在雙親委派機制下,加載請求會被傳遞到頂層的啟動類加載器,啟動類加載器發現這個名字的類已經加載過了,就會直接回傳已經加載過的Integer.class,這樣就可以防止核心API被修改,
4 OSGI
OSGI(Open Service Gateway Initiative)是面向java的動態模型系統,OSGI能夠提供無需重啟的動態改造功能,基于OSGI的程式很可能可以實作模塊級的熱插拔功能,當程式進行升級時,只需停用,重新安裝然后啟動程式的一部分,但并非所有的程式都適合OSGI架構,其在提供強大功能的同時也提高了復雜度,因為OSGI不支持雙親委派機制,
eclipse就是基于OSGI技術來構建的,
OSGI每個模塊都自己的類,每個模塊可以宣告其需要模塊的類(匯入),也可以宣告自己的類以供其它模塊使用(匯出),每個模塊都有自己的類加載器,他負責加載本模塊的類,對于非本模塊的類:核心庫的類會代理給父類加載器(通常是啟動類加載器),其他模塊匯入的類則代理給對應模塊由其加載,對于java開頭的類,默認都是由父類加載器完成的,可以通過org.osgi.framework.bootdelegation設定某些包或者類必須由父類加載器加載,如設定org.osgi.framework.bootdelegation = com.my.* 此時com.my下的所有類都由父類加載器進行加載
例如由兩個模塊:M-A和M-B,分別有類C-A和C-B,C-A繼承自C-B,M-A啟動時,對C-A進行加載,因為繼承關系繼而需要加載C-B,此時由于M-A宣告了C-B是由M-B匯入的,那么就會將C-B的加載交由M-B執行,M-B對C-B進行加載,所得的類實體可以被所有宣告匯入此類的模塊使用,
參考資料:
jvm之java類加載機制和類加載器(ClassLoader)的詳解
深入探討 Java 類加載器
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/216361.html
標籤:其他
上一篇:論文閱讀:Content Aware Video Compression:-An Approach To VOS Algorithm
