轉載請注明原文地址:http://www.cnblogs.com/ygj0930/p/6536048.html
虛擬機把描述類的數據從Class文件加載到內存,並對數據進行校驗、轉換解析和初始化,最終成為被虛擬機直接使用的Java對象,這就是JVM的類加載機制。
Java天生的可動態擴展的語言特性就是依賴運行期的動態加載和動態連接實現的。
一:類的生命周期
類的生命周期包括7個部分:加載——驗證——准備——解析——初始化——使用——卸載
其中,驗證——准備——解析 稱為連接階段。除了解析外,其他階段是順序發生的,而解析可以與這些階段交叉進行,因為Java支持動態綁定(晚期綁定),需要運行時才能確定具體類型。
二:類的初始化觸發
類的加載機制沒有明確的觸發條件,但是有5種情況下必須對類進行初始化,那么 加載——驗證——准備 就必須在此之前完成了。
1:new、getstatic、putstatic、invokestatic這4個 字節碼指令 時對類進行初始化(即:實例化對象、讀寫靜態對象、調用靜態方法時,進行類的初始化);
2:使用反射機制對類進行調用時,進行類的初始化;
3:初始化一個類,其父類沒有初始化時,先初始化其父類;
4:虛擬機啟動時,初始化一個執行主類;
5:使用JDK1.7的動態語言支持時,如果MethodHandle實例的解析結果為REF_getstatic、REF_putstatic、REF_invokestatic的方法句柄(即:讀寫靜態對象或者調用靜態方法),則初始化該句柄對應類;
一般,以上5種情況最常見的是前三種:實例化對象、讀寫靜態對象、調用靜態方法、反射機制調用類、調用子類觸發父類初始化。
三:類的加載過程
從用戶角度來說,類(對象)的生命周期只需籠統理解為“加載——使用——卸載”即可,無需太過深入。所以,這里的類加載過程就是我們說的 加載——驗證——准備——解析——初始化 這五個使用前的階段。
1:加載
加載階段,虛擬機需要完成三件事:通過類名字獲取類的二進制字節流——將字節流的內容轉存到方法區——在內存中生成一個Class對象作為該類方法區數據的訪問入口。
其中,第一步:通過類名獲取類的二進制字節流是通過類加載器來完成的。其加載過程使用“雙親委派模型”:
類加載器的層次結構為:
啟動類加載器:加載系統環境變量下JAVA_HOME/lib目錄下的類庫。
擴展類加載器:加載JAVA_HOME/lib/ext目錄下的類庫。
應用程序類加載器(系統類加載器):加載用戶類路徑Class_Path指定的類庫。(我們可以在使用第三方插件時,把jar包添加到ClassPath后就是使用了這個加載器)
自定義加載器:如果需要自定義加載時的規則(比如:指定類的字節流來源、動態加載時性能優化等),可以自己實現類加載器。
雙親委派模型是指:當一個類加載器收到類加載請求時,不會直接加載這個類,而是把這個加載請求委派給自己父加載器去完成。如果父加載器無法加載時,子加載器才會去嘗試加載。
采用雙親委派模型的原因:避免同一個類被多個類加載器重復加載。
2:驗證
確保class文件的二進制字節流中包含的信息符號虛擬機要求,包括:文件格式驗證、元數據驗證(數據語義分析)、字節碼驗證(數據流語義合法性)、符號引用驗證(符號引用的匹配性校驗,確保解析能正確執行)
3:准備
為類變量(靜態變量)在方法區分配內存,並設置零值。注意:這里是類變量,不是實例變量,實例變量是對象分配到堆內存時根據運行時動態生成的。
4:解析
把常量池中的符號引用解析為直接引用:根據符號引用所作的描述,在內存中找到符合描述的目標並把目標指針指針返回。
5:初始化
真正開始執行Java程序代碼,該步執行<clinit>方法根據代碼賦值語句,對 類變量和其他資源 進行初始化賦值。
<clinit>方法:編譯器自動收集類中所有 類變量的賦值語句和靜態語句合並而成,收集的順序是在程序代碼出現的順序。所以,靜態語句中只能訪問到定義在靜態語句塊之前的變量,在其之后的變量可以賦值(相當於新建並賦值了)但不可以訪問(因為還沒出現)。
注:由此步我們就可以得知,我們在分析向上轉型的例子時的程序代碼的運行順序了:父類靜態內容——子類靜態內容——父類構造——子類構造——子類方法 。
在經歷了上面5步“加載”階段后,才真正地可以使用class對象或者使用實例對象。使用過后,不再需要用到該類的class對象或者實例對象時,就會把類卸載掉(發生在方法區的垃圾回收:無用類的卸載)。
四:對象的生命周期
對象是由類創建出來的,所以對象的生命周期就是包含在類的生命周期中:
類加載(5步)——創建類的實例對象——使用對象——對象回收——類卸載