1. 程式人生 > >理解JVM之類加載機制

理解JVM之類加載機制

反射 fin 規範 賦值 數組 class文件 構造 數據訪問 構造器

  類完整的生命周期包括加載,驗證,準備,解析,初始化,使用,卸載,七個階段.其中驗證,準備,解析統稱為連接,類的卸載在前面的關於垃圾回收的博文中已經介紹.

  加載,驗證,準備,初始化,卸載這五個階段的順序是確定的,類的加載必須按照這種順序按部就班的來,而解析階段不一定,它可以在初始化階段之後開始,這是為了支持java的運行時動態綁定.值得註意的是,上述的五個階段只是按部就班的"開始",並不是按部就班的"進行"或者"完成",因為這些階段通常是互相交叉混合進行的,通常會在一個階段執行的過程調用,激活另一個階段.

  在java虛擬機規範中並沒有進行強制約束什麽時候加載類,這是交給虛擬機具體實現自由把握,但是對於初始化階段,虛擬機規範有嚴格規定:

  1.使用new實例化對象或者讀取,設置一個類的靜態字段(被final或者已在編譯器吧結果放入常量池的靜態字段除外)的時候以及調用一個類的靜態方法的時候.

  2.使用反射調用的時候,如果類沒初始化,需要進行初始化

  3,當初始化一個類的時候,父類沒有初始化,則需要先初始化父類

  4.虛擬機啟動時,用戶需要制定一個執行類(包含main方法的那個類),虛擬機會先初始化這個主類.

  5.使用動態語言支持時,如果一個java.lang.MethodHandle實例最後解析結果REF_getStatic,REF_putStatic,REF_invokeStatic的方法句柄,並且這個句柄鎖對應的類沒有初始化,則初始化這個類.

  下面具體介紹類加載的過程.

1.加載

  加載階段,虛擬機需要完成3件事情:

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

  2) 將這個字節流鎖代表的靜態存儲結構轉化為方法區的運行時數據結構

  3) 在內存中生成帶包這個類的Class對象,作為方法區這個類的各種數據訪問入口.

  對於數組類而言,情況不同,數組類本身不通過類加載器創建,它是由java虛擬機直接創建.數組類的創建過程遵循以下規則:

  1) 如果數組的組件類型(指數組去掉一個維度的類型)是引用類,那就采取上述的方法來加載這個組件類型,數組C將在加載該組件類型的類加載器的類名空間上被標識

  2) 如果數組的組件類型不是引用類型(例如int[]),虛擬機會將數組標記為與引導類加載器關聯

  3) 數組類的可見性與他的組件類型的可見性一直,如果組件類型不是引用類型,那數組類的可見性將默認為public.

  加載完成後,外部二進制字節流就按照虛擬機需要的格式存儲在方法區中,然後實例化一個Class類的對象作為程序訪問方法區中的這些類型數據的外部接口.

2.驗證

  驗證是連接的第一步,這一階段為了確保Class文件的字節流中包含的信息符合當前虛擬機的要求,並且不會危害虛擬機自身的安全.驗證分為四個階段:文件格式驗證,元數據驗證,字節碼驗證,符號引用驗證

  1) 文件格式驗證是驗證字節流是否符合Class文件格式的規範,並且能夠被當前版本的處理機處理.

  2) 元數據驗證是對字節碼描述的信息進行語義分析,以保證其描述的信息符合java語言規範的要求.

  3) 字節碼驗證是只能怪過驗證過程最復雜的階段,主要是通過數流和控制流分析,確定程序語義是合法的,符合邏輯的.這個階段對類的方法體進行校驗分析,保證被校驗類的方法在運行時不會出現危害虛擬機安全的事件.

  4) 符號引用驗證發生在虛擬機將符號引用轉化為直接引用的時候.這個動作是在解析階段發生.

3.準備

  這個階段是正式為類變量分配內存並設置類變量初始值的階段,這些變量鎖使用的內存都在方法區中分配.值得註意的是這裏進行分配內存的僅包括類變量(被static修飾的變量),不包括實例變量,其次這裏設置初始值是將值設為零值,例如一個整型類變量設置的零值是0,而不是代碼中寫出來的值.將代碼寫出來的值賦值給這個變量是初始化階段做的事情

4.解析

  解析階段是虛擬機將常量池的符號引用替換為直接引用的過程.解析包括類或接口的解析,字段解析,類方法解析,接口方法解析

5.初始化

  初始化是類加載的最後一步,在準備階段,變量已經賦過一次系統要求的初始值,而在初始化階段,則根據程序員通過程序制定的主觀計劃去初始化類變量和其他資源,或者換一種說法:初始化階段是執行類構造器<clinit>()方法的過程.

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

  <cliinit>()方法還有許多規定,請自行查找相關資料

6.類與類加載器

  類加載器雖然只用於實現類加載動作,但是在java程序中的還有其他作用.對於任意一個類加載器,都有一個獨立的類名空間,而被這個類加載器加載出來的類,其類名會被標記上類加載器的類名空間,則兩個特性決定了一個類在虛擬機中的唯一性.也就是說我們要比較兩個類是否相等,前提是要這兩個類是同一個類加載器加載出來的.否則就算是同一個class文件加載出來的這兩個的也是不相等的.因為instanceof這個關鍵字會將兩個類判定為不是同一個類,

7.雙親委派模型

  這個模型的工作過程是:如果一個類加載器收到了類加載請求,它首先不會自己嘗試加載這個類,而是吧這個請求委派給父類加載器去完成.因此所有的加載請求最終都會送到頂層的啟動類加載器,只有父類反饋無法完成類加載讓子加載器才會嘗試加載.這個模型使java的類不會因為類加載器不同而產生不同的相同類.

理解JVM之類加載機制