- 類加載器負責從文件系統(tǒng)或網(wǎng)絡(luò)中加載Class文件,Class文件在文件開頭有特定的文件標識(字節(jié)碼文件都以CA FE BA BE標識開頭稱為魔數(shù))。
- ClassLoader只負責Class文件的加載,至于它是否可以運行,則由ExecutionEngine決定。
- 加載的類信息存于一塊稱為方法區(qū)的內(nèi)存空間。除了類的信息外,方法區(qū)還會存放運行時常量池(常量池在運行時加載到內(nèi)存里)信息,可能還包括字符串字面量和數(shù)字常量(這部分常量信息是Class文件中常量池部分的內(nèi)存映射)
類的加載過程
加載(Loading)->
鏈接Linking【驗證(Verification)->準備(Preparation)->解析(Resolution)】->
初始化(Initialization)
加載
1.通過一個類的全限定名(全類名)獲取此類的二進制字節(jié)流。
2.將這個字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)(落地實現(xiàn)在jdk7及以前叫永久代,之后叫元空間)的運行時數(shù)據(jù)結(jié)構(gòu)。
3.在內(nèi)存中生成一個代表這個類的java.lang.Class對象,作為方法區(qū)這個類的各種數(shù)據(jù)的訪問入口。
JVM支持兩種類型的類加載器,分別為引導(dǎo)類加載器(Bootstrap ClassLoader)和自定義類加載器(User-Defined ClassLoader).
Java虛擬機規(guī)范中將所有派生于抽象類ClassLoader的類加載器都劃分為自定義類加載器。
引導(dǎo)類加載器Bootstrap ClassLoader
?這個類使用C/C++語言實現(xiàn),嵌套在JVM內(nèi)部
?它用來加載Java的核心類庫,用于提供JVM自身需要的類
?并不繼承自java.lang.ClassLoader,沒有父加載器
?負責加載擴展類和應(yīng)用程序(系統(tǒng))類加載器,并指定為他們的父類加載器
?處于安全考慮,Bootstrap啟動類加載器只加載包名為java、javax、sun等開頭的類擴展類加載器Extension ClassLoader
?Java語言編寫,由sun.misc.Launcher$ExtClassLoader實現(xiàn)
?派生于ClassLoader類
?父類加載器為啟動類加載器
?從java.ext.dirs系統(tǒng)屬性所指定的目錄中加載類庫,或從JDK的安裝目錄的jre/lib/ext子目錄(擴展目錄)下加載類庫。如果用戶創(chuàng)建的JAR放在此目錄下,也會自動有擴展類加載器加載。系統(tǒng)類加載器(應(yīng)用程序類加載器)AppClassLoader
?Java語言編寫,由sun.misc.Launcher$AppClassLoader實現(xiàn)
?派生于ClassLoader類
?父類加載器為擴展類加載器
?它負責加載環(huán)境變量classpath(自定義的類的生成路徑)或系統(tǒng)屬性java.class.path指定路徑下的類庫
?該類加載器是程序中默認的類加載器,一般來說,Java應(yīng)用的類都是由它來完成加載
?通過ClassLoader#getSystemClassLoader()方法可以獲取到該類加載器
在JVM中表示兩個class對象是否為同一個類存在兩個必要條件:
?類的完整類名必須一致,包括包名
?加載這個類的ClassLoader(指向ClassLoader實例對象)必須相同
對類加載器的引用
JVM必須知道一個類型是由引導(dǎo)加載器加載的還是由用戶類加載器加載的。如果一個類型是由用戶類加載器加載的,那么JVM會將這個類加載器的一個引用作為類型信息的一部分保存在方法區(qū)中。當解析一個類型到另一個類型的引用的時候,JVM需要保證這兩個類型的加載器是相同的。
鏈接
驗證
- 目的在于確保Class文件的字節(jié)流中包含的信息符合當前虛擬機要求,保證被加載類的正確性,不會危害虛擬機自身安全。
- 主要包括四種驗證:文件格式驗證,元數(shù)據(jù)驗證,字節(jié)碼驗證,符號引用驗證。
準備
- 為類變量分配內(nèi)存并且設(shè)置該類變量的默認初試值,即零值。
- 這里不包含用final修飾的static,因為final在編譯時就會分配了,準備階段會顯示初始化
- 這里不會為實例變量分配初始化,類變量會分配在方法區(qū)中,而實例變量時會隨著對象一起分配到Java堆中。
解析
- 將常量池內(nèi)的符號引用轉(zhuǎn)換為直接引用的過程。
- 符號引用就是一組符號來描述所引用的目標。符號引用的字面量形式明確定義在《Java虛擬機規(guī)范》的Class文件格式中。
- 直接引用就是直接指向目標的指針、相對偏移量或一個間接定位到目標的句柄。
- 解析操作往往會伴隨著JVM在執(zhí)行完成初始化之后再執(zhí)行
- 解析動作主要針對類或接口、字段、類方法、接口方法、方法類型等。對應(yīng)常量池中的CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info等。
初始化
- 初始化階段就是指向類構(gòu)造器方法
<clinit>()
的過程 - 此方法不需要用戶定義,是javac編譯器自動收集類中的所有類變量的賦值動作和靜態(tài)代碼塊中的語句合并而來。
- 構(gòu)造器方法中指令按語句在源文件中出現(xiàn)的順序執(zhí)行。
-
<clinit>()
不同于類的構(gòu)造器。 - 若該類具有父類,JVM會保證子類的
<clinit>()
執(zhí)行前,父類的<clinit>()
已經(jīng)執(zhí)行完畢。 - 虛擬機必須保證一個類的
<clinit>()
方法在多線程下被同步加鎖。