我們已經(jīng)講過 JVM 相關的很多常見知識點莱革,感興趣的朋友可以在我的往期文章中查看。接下來將繼續(xù)為各位帶來 JVM 類加載機制晨炕。關注我的公眾號「Java面典」了解更多 Java 相關知識點。
類生命周期
一個 Java 類在 JVM 中的整個生命周期包括:加載(Loading)、驗證(Verification)净捅、準備(Preparation)、解析(Resolution)辩块、初始化(Initializationg)蛔六、使用(Using)和卸載(Unloading)荆永。
加載
目的
在該階段虛擬機會在內(nèi)存中生成一個代表這個類的 java.lang.Class 對象,以此作為方法區(qū)這個類的各種數(shù)據(jù)的入口国章。
加載源
類的加載可以通過以下幾個方式進行:
- Class 文件中獲染咴俊;
- 從ZIP包中讀取液兽,包括 JAR 骂删、EAR 、WAR 等格式的壓縮包四啰;
- 從網(wǎng)絡中獲取宁玫,如 Applet;
- 運行時計算生成柑晒,動態(tài)代理技術(shù)(java.lang.reflect.Porxy)欧瘪;
- 其他文件生成,如由 JSP 文件生成對應的 Class文件敦迄;
- 從數(shù)據(jù)庫中讀取恋追,如 SAP NetWeaver 這樣的中間件服務器。
驗證
目的
為了確保 Class 文件的字節(jié)流中包含的信息符合當前虛擬機的要求罚屋,并且不會危害虛擬機自身的安全苦囱。
驗證動作
驗證階段主要包含下面 4 個階段的校驗動作:
- 文件格式驗證。驗證字節(jié)流是否符合 Class 文件格式的規(guī)范脾猛,并且能被當前版本的虛擬機處理撕彤;
- 元數(shù)據(jù)驗證。對類的元數(shù)據(jù)信息進行語義校驗猛拴,保證不存在不符合 Java 語言規(guī)范的元數(shù)據(jù)信息羹铅;
- 字節(jié)碼驗證。對類的方法體進行校驗分析愉昆,保證被校驗類的方法在運行時不會做出危害虛擬機安全的事件职员;
- 符號引用驗證。發(fā)生在虛擬機符號引用轉(zhuǎn)化為直接引用的時候跛溉,對類自身以外的信息(常量池中的各種符號引用)進行匹配性校驗焊切。
準備
準備階段是正式為類變量分配內(nèi)存并設置類變量的初始值階段,即在方法區(qū)中分配這些變量所使用的內(nèi)存空間芳室。
注意
這里所說的初始值概念专肪,比如一個類變量定義為:
public static int v = 8080;
實際上變量 v 在準備階段過后的初始值為 0 而不是 8080,將 v 賦值為 8080 的 put static 指令是程序被編譯后堪侯,存放于類構(gòu)造器<client>方法之中嚎尤。
但是注意如果聲明為:
public static final int v = 8080;
在編譯階段會為 v 生成 ConstantValue 屬性,在準備階段虛擬機會根據(jù) ConstantValue 屬性將 v 賦值為 8080伍宦。
解析
解析階段是指虛擬機將常量池中的符號引用替換為直接引用的過程芽死。
符號引用
符號引用與虛擬機實現(xiàn)的布局無關乏梁,引用的目標并不一定要已經(jīng)加載到內(nèi)存中。各種虛擬機實現(xiàn)的內(nèi)存布局可以各不相同收奔,但是它們能接受的符號引用必須是一致的掌呜,因為符號引用的字面量形式明確定義在 Java 虛擬機規(guī)范的 Class 文件格式中。
直接引用
直接引用可以是指向目標的指針坪哄,相對偏移量或是一個能間接定位到目標的句柄质蕉。如果有了直接引用,那引用的目標必定已經(jīng)在內(nèi)存中存在翩肌。
解析動作
解析動作主要針對類或接口(CONSTANT_Class_info)模暗、字段(CONSTANT_Fieldref_info)、類方法(CONSTANT_Methodref_info)念祭、接口方法(CONSTANT_InterfaceMethodref_info)兑宇、方法類型(CONSTANT_MethodType_info)、方法句柄(CONSTANT_MethodHandle_info)和調(diào)用點限定符(CONSTANT_InvokeDynamic_info) 7 類符號引用進行粱坤。
初始化
類初始化是類加載過程的最后一步隶糕,此時才真正開始執(zhí)行類中定義的 Java 程序代碼(或者說是字節(jié)碼)。
類構(gòu)造器
初始化階段是執(zhí)行類構(gòu)造器<client>()方法的過程站玄。<client>()方法是由編譯器自動收集類中的類變量的賦值操作和靜態(tài)語句塊中的語句合并而成的枚驻。虛擬機會保證子<client>()方法執(zhí)行之前,父類的<client>()方法已經(jīng)執(zhí)行完畢株旷,如果一個類中沒有對靜態(tài)變量賦值也沒有靜態(tài)語句塊再登,那么編譯器可以不為這個類生成<client>()方法。
注意以下幾種情況不會執(zhí)行類初始化:
- 通過子類引用父類的靜態(tài)字段晾剖,只會觸發(fā)父類的初始化锉矢,而不會觸發(fā)子類的初始化;
- 定義對象數(shù)組齿尽,不會觸發(fā)該類的初始化沽损;
- 常量在編譯期間會存入調(diào)用類的常量池中,本質(zhì)上并沒有直接引用定義常量的類循头,不會觸發(fā)定義常量所在的類绵估;
- 通過類名獲取 Class 對象,不會觸發(fā)類的初始化贷岸;
- 通過 Class.forName 加載指定類時,如果指定參數(shù) initialize 為 false 時磷雇,也不會觸發(fā)類初始化偿警,其實這個參數(shù)是告訴虛擬機,是否要對類進行初始化唯笙;
- 通過 ClassLoader 默認的 loadClass 方法螟蒸,也不會觸發(fā)初始化動作盒使。
雙親委派
類加載器
定義
虛擬機設計團隊把加載動作放到 JVM 外部實現(xiàn),以便讓應用程序決定如何獲取所需的類七嫌,實現(xiàn)這個動作的代碼塊稱為類加載器少办。
分類
JVM 提供了 3 種類加載器,啟動類加載器(Bootstrap ClassLoader)诵原、擴展類加載器(Extension ClassLoader)英妓、應用程序類加載器(Application ClassLoader)。除此之外绍赛,用戶還可實現(xiàn)自定義類加載器蔓纠。其作用及其應用范圍如下:
- 啟動類加載器:負責加載 JAVA_HOME\lib 目錄中的,或通過-Xbootclasspath 參數(shù)指定路徑中的吗蚌,且被虛擬機認可(按文件名識別腿倚,如 rt.jar)的類;
- 擴展類加載器:負責加載 JAVA_HOME\lib\ext 目錄中的蚯妇,或通過 java.ext.dirs 系統(tǒng)變量指定路徑中的類庫敷燎;
- 應用程序類加載器:負責加載用戶路徑(classpath)上的類庫。
定義
類加載器之間的層次關系箩言,稱為類加載器的雙親委派模型硬贯。雙親委派模型要求除了頂層的啟動類加載器外,其余的類加載器都應該有自己的父類加載器分扎。
工作過程
雙親委派工作過程澄成,可以有簡單的一句話概括——父加載類優(yōu)先加載,其具體流程為:
- 當一個類收到了類加載請求畏吓;
- 將這個請求委派給父類去完成墨状,一一層層向上委派;
- 當父類加載器反饋自己無法完成這個請求的時候(在它的加載路徑下沒有找到所需加載的 Class)菲饼;
- 子類加載器才會嘗試自己去加載肾砂。
采用雙親委派的一個好處是比如加載位于 rt.jar 包中的類 java.lang.Object,不管是哪個加載器加載這個類宏悦,最終都是委托給頂層的啟動類加載器進行加載镐确,這樣就保證了使用不同的類加載器最終得到的都是同樣一個 Object 對象。