程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> java虛擬機類加載機制---《深入理解java虛擬機》讀書筆記

java虛擬機類加載機制---《深入理解java虛擬機》讀書筆記

編輯:JAVA綜合教程

java虛擬機類加載機制---《深入理解java虛擬機》讀書筆記


Java虛擬機類加載機制是把Class類文件加載到內存,並對Class文件中的數據進行校驗、轉換解析和初始化,最終形成可以被虛擬機直接使用的java類型的過程。

java可動態擴展的語言特性就是依賴運行期動態加載和動態鏈接這個特點實現的。

類從被加載到虛擬機內存中開始,到卸載出內存為止,整個生命周期包括:加載、驗證、准備、解析、初始化、使用和卸載七個階段,其中驗證、准備、解析3個部分統稱為連接,加載、驗證、准備、初始化、卸載這5個階段的順序是一定的,類的加載過程必須按照這種順序按部就班的開始,但是並不一定按部就班的執行,因為這些階段通常都是互相交叉的混合式進行的,通常會在一個階段執行的過程中調用、激活另外一個階段,而且解析階段在某些情況下可以在初始化階段之後進行,這是為了支持java語言的動態綁定。

虛擬機規范嚴格規定了有且只有5種情況必須立即對類進行初始化:(稱為對類的主動引用)

(1)遇到new、getstatic、putstatic和invokestatic這4條字節碼指令時,如果類沒有進行過初始化,則需要先觸發器初始化,

(2)使用java.lang.reflect包的方法對類進行反射調用的時候,

(3)當初始化一個類的時候,如果發現其父類還沒有進行過初始化,則需要先觸發其父類的初始化,

(4)當虛擬機啟動時,用戶需要指定一個要執行的主類,虛擬機會先初始化這個主類,

(5)當使用jdk1.7的動態語言進行支持的時候,如果一個java.lang.invoke.methodHandle實例最後的解析結果ref_getstatic等的方法句柄,並且這個方法句柄所對應的類沒有進行初始化,則需要先觸發其初始化。

除此之外,所有引用類的方式都不會觸發初始化,稱為被動引用:

(1)通過子類引用父類的靜態字段,不會導致子類初始化,對於靜態字段,只有直接定義這個字段的類才會被初始化,

(2)通過數組定義來引用類,不會觸發此類的初始化

(3)常量在編譯階段會存入調用類的常量池中,本質上並沒有直接引用到定義常量的類,因此不會觸發定義常量的類的初始化,

接口的加載和類的加載稍有一些不同,但是接口也有初始化的過程,這一點與類是一致的,上面的代碼都是用靜態語句塊“static{}”來輸出初始化信息,而接口不能使用“static{}”語句塊,但編譯器仍然會為接口生成“()”類構造器,用於初始化接口中定義的成員變量,真正的區別是類在初始化的過程中要求其父類全部都已經初始化過了,但是一個接口在初始化時,並不要求其父接口全部都進行了初始化,只有在真正使用到了父接口的時候才會初始化,

類加載的過程:

在加載階段,java虛擬機需要完成以下3件事:

a.通過一個類的全限定名來獲取定義此類的二進制字節流。

b.將定義類的二進制字節流所代表的靜態存儲結構轉換為方法區的運行時數據結構。

c.在java堆中生成一個代表該類的java.lang.Class對象,作為方法區數據的訪問入口。

一個非數組類的加載階段(即加載階段中獲取類的二進制字節流的動作)是開發人員可以控制的,但是對於數組類,情況有所不同,數組類本身不通過類加載器創建,他是由java虛擬機直接進行創建的,但是數組類的元素類型最終是要靠類加載器去創建,一個數組類的創建過程遵循的規則參考p215頁

加載階段完成後,虛擬機外部的二進制字節流就按照虛擬機所需的格式存儲在方法區中,方法區的數據存儲格式由虛擬機自行定義,

驗證階段:

驗證是連接階段的第一步,目的是為了確保class文件的字節流中包含的心細符合當前虛擬機的要求,並且不會危害虛擬機自身的安全,java語言本身是相對安全的語言,使用純粹的java代碼無法做到諸如訪問數組邊界以外的數據,如果這樣做了,編譯器將拒絕編譯,但是class文件並不一定要求用java源碼編譯而來,可以使用任何途徑產生,所以虛擬機如果不檢查輸入的字節流,對其完全信任的話,很可能會因為載入了有害的字節流而導致系統崩潰,所以驗證是虛擬機對自身保護的一項重要工作。

驗證的4個階段:

1.文件格式驗證:

驗證是否以魔數0xcafebabe開頭,主、次版本號是否在當前虛擬機處理范圍之內,常量池的常量是否有不被支持的敞亮類型等,主要目的是保證輸入的字節流能正確的解析並存儲於方法區之內,

2.元數據驗證:

對字節碼描述的信息進行語義分析,保證信息符合java語言規范要求,主要包括的驗證點為:這個類是否有父類,這個類的父類是否繼承了不允許被繼承的類,如果這個類不是抽象類,是否實現了其父類或接口之中要求實現的所有方法等

3.字節碼驗證:

主要目的是通過數據流和控制流分析,確定程序語義是合法的、符合邏輯的,第二階段對數據信息中的數據類型做完校驗後,這個階段將對類的方法體進行校驗分析,如果一個類的方法體通過了字節碼驗證,也不能說明其一定是安全的,

由於數據流驗證的高復雜性,避免消耗過多的時間,虛擬機在方法體的code屬性的屬性表增加了一項名為“stackmaptable”的屬性,描述了方法體中所有的基本塊開始時本地變量表和操作棧應有的狀態,就不用根據程序推導這些狀態的合法性 p218

4.符號引用驗證:

目的是確保解析動作能正常執行,發生在虛擬機將符號引用轉換為直接引用的時候,

准備階段:

准備階段是正式為類變量分配內存並設置類變量初始值的階段,首先這裡的類變量不包括實例變量,實例變量將會在對象實例化的時候隨著對象一起分配在java堆中,初始值一般是數據類型的零值,但是如果類字段的字段屬性表中存在constantvalue(不變)屬性,那麼就會在准備階段初始化為屬性所指定的值,

解析階段:

解析是虛擬機將常量池內的符號引用替換為直接引用的過程,符號引用與虛擬機實現的內存布局無關,對同一個符號引用進行多次解析是很常見的事情,除invokedynamic外,虛擬機可以對第一次解析的結果進行緩存,從而避免解析動作重復進行,但是對於invokedynamic指令,因為其目的是用於動態語言支持,

四種引用的解析過程:

1.類或接口的解析:

2.字段解析:

3.類方法解析:

4.接口方法解析

初始化階段:

類初始化階段是類加載的最後一步,到了初始化階段,才真正開始執行類中定義的java程序代碼,在准備階段,變量已經賦過一次系統要求的初始值,而在初始化階段,是執行類構造器()方法的過程,

首先看一下()方法執行過程中的一些可能影響程序運行行為的特點和細節:

(1)()方法是由編譯器自動收集類中的所有類變量的賦值動作和靜態語句塊中的語句合並產生,編譯器收集的順序是由語句在源文件中出現的順序所決定的,靜態語句塊中只能訪問到定義在靜態語句塊之前的變量,定義在他之後的變量,在前面的靜態語句塊可以賦值,但是不能訪問,

(2)()方法與類的構造函數不同,他不需要顯式的調用父類構造器,虛擬機會保證在子類的()方法執行以前,父類的方法已經執行完畢,

(3)由於父類的()方法先執行,則父類的靜態語句塊要優先於子類的變量賦值操作,

(4)()方法對於類或接口來說不是必須的,如果一個類中沒有定義靜態語句塊,沒有對變量進行賦值操作,則編譯器不會為這個類產生這個方法,

(5)執行接口中的()方法不需要先執行父接口的()方法

Java虛擬機的類加載是通過類加載器實現的,對於任意一個類,都需要由加載它的類加載器和這個類本身一同確立其在java虛擬機中的唯一性,也就是說比較兩個類是否相等,只有在這兩個類是由同一個類加載器加載的前提下才有意義,

(1).BootStrap ClassLoader:啟動類加載器,負責加載存放在%JAVA_HOME%\lib目錄中的,或者通被-Xbootclasspath參數所指定的路徑中的,並且被java虛擬機識別的(僅按照文件名識別,如rt.jar,名字不符合的類庫,即使放在指定路徑中也不會被加載)類庫到虛擬機的內存中,啟動類加載器無法被java程序直接引用。

(2).Extension ClassLoader:擴展類加載器,由sun.misc.Launcher$ExtClassLoader實現,負責加載%JAVA_HOME%\lib\ext目錄中的,或者被java.ext.dirs系統變量所指定的路徑中的所有類庫,開發者可以直接使用擴展類加載器。

(3).Application ClassLoader:應用程序類加載器,由sun.misc.Launcher$AppClassLoader實現,負責加載用戶類路徑classpath上所指定的類庫,是類加載器ClassLoader中的getSystemClassLoader()方法的返回值,開發者可以直接使用應用程序類加載器,如果程序中沒有自定義過類加載器,該加載器就是程序中默認的類加載器。

注意:上述三個JDK提供的類加載器雖然是父子類加載器關系,但是沒有使用繼承,而是使用了組合關系。

從JDK1.2開始,java虛擬機規范推薦開發者使用雙親委派模式(ParentsDelegation Model)進行類加載,其加載過程如下:

(1).如果一個類加載器收到了類加載請求,它首先不會自己去嘗試加載這個類,而是把類加載請求委派給父類加載器去完成。

(2).每一層的類加載器都把類加載請求委派給父類加載器,直到所有的類加載請求都應該傳遞給頂層的啟動類加載器。

(3).如果頂層的啟動類加載器無法完成加載請求,子類加載器嘗試去加載,如果連最初發起類加載請求的類加載器也無法完成加載請求時,將會拋出ClassNotFoundException,而不再調用其子類加載器去進行類加載。

雙親委派 模式的類加載機制的優點是java類它的類加載器一起具備了一種帶優先級的層次關系,越是基礎的類,越是被上層的類加載器進行加載,保證了java程序的穩定運行。雙親委派模式的實現:
[java]view plaincopy
  1. protectedsynchronizedClassloadClass(Stringname,Booleanresolve)throwsClassNotFoundException{
  2. //首先檢查請求的類是否已經被加載過
  3. Classc=findLoadedClass(name);
  4. if(c==null){
  5. try{
  6. if(parent!=null){//委派父類加載器加載
  7. c=parent.loadClass(name,false);
  8. }
  9. else{//委派啟動類加載器加載
  10. c=findBootstrapClassOrNull(name);
  11. }
  12. }catch(ClassNotFoundExceptione){
  13. //父類加載器無法完成類加載請求
  14. }
  15. if(c==null){//本身類加載器進行類加載
  16. c=findClass(name);
  17. }
  18. }
  19. if(resolve){
  20. resolveClass(c);
  21. }
  22. returnc;
  23. }

若要實現自定義類加載器,只需要繼承java.lang.ClassLoader類,並且重寫其findClass()方法即可。java.lang.ClassLoader類的基本職責就是根據一個指定的類的名稱,找到或者生成其對應的字節代碼,然後從這些字節代碼中定義出一個Java類,即java.lang.Class類的一個實例。除此之外,ClassLoader還負責加載Java應用所需的資源,如圖像文件和配置文件等,ClassLoader中與加載類相關的方法如下:

方法

說明

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved