Java類加載是指將編譯好的class文件加載至JVM內(nèi)存, 形成可供JVM使用的Java實(shí)例, 這個(gè)過程叫做類的加載。

1、類的加載過程
類的加載過程包括了加載, 連接, 初始化3個(gè)階段, 其中連接階段又分為驗(yàn)證,準(zhǔn)備,解析 3個(gè)階段, 總體上可以分為加載、驗(yàn)證、準(zhǔn)備、解析、初始化五個(gè)階段。
這五個(gè)階段中, 除過解析階段, 其他階段的順序都是固定的, 而解析階段則不一定, 有些時(shí)候晚于初始化階段發(fā)生, 這是為了支持Java中的運(yùn)行時(shí)綁定。其他幾個(gè)階段都是按順序開始, 但不一定按順序進(jìn)行或完成, 往往是交叉運(yùn)行, 在一個(gè)階段中調(diào)用下一個(gè)階段。

1.1、 類的加載: 查找并加載類的二進(jìn)制數(shù)據(jù)
加載時(shí)類加載過程的第一個(gè)階段, 在加載階段, 虛擬機(jī)主要完成3件事情:
- 根據(jù)類的全限定名來獲取類的二進(jìn)制數(shù)據(jù)。
- 將這個(gè)類的二進(jìn)制字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)結(jié)構(gòu)。
- 在Java堆中生成一個(gè)代表這個(gè)類的java.lang.Class對象, 作為方法區(qū)中這些數(shù)據(jù)的訪問入口。

類的加載并不時(shí)該類被用到時(shí)才會被加載, JVM規(guī)范允許類加載器在預(yù)料某個(gè)類可能被使用時(shí)就加載該類。 如果加載時(shí), 該類對應(yīng)的class文件不存在或者錯(cuò)誤, 類加載器必須在首次使用該類時(shí)報(bào)告一個(gè)類加載錯(cuò)誤, 如果類從未被使用, 則不會報(bào)錯(cuò)。
加載class的方式:
- 從本地文件系統(tǒng)直接加載
- 通過網(wǎng)絡(luò)下載.class文件
- 從zip或jar等歸檔文件中加載文件
- 從專有數(shù)據(jù)庫中加載class文件
- 從java源文件中動(dòng)態(tài)編譯class文件
1.2、 驗(yàn)證: 確保被加載類的正確性
驗(yàn)證時(shí)連接階段的第一個(gè)階段,主要時(shí)保證類的合法性, 確保Class字節(jié)流包含的信息是否符合當(dāng)前虛擬機(jī)的要求, 并且不會危害虛擬機(jī)本身的的安全, 驗(yàn)證階段大致會完成4個(gè)階段的驗(yàn)證動(dòng)作:
- 文件格式驗(yàn)證: 驗(yàn)證字節(jié)流是否符合Class文件的規(guī)范。
- 元數(shù)據(jù)驗(yàn)證: 驗(yàn)證字節(jié)流描述的信息是否符合Java語言規(guī)范。
- 字節(jié)碼驗(yàn)證: 通過數(shù)據(jù)流和控制流分析, 確定程序語義是否合法,符合規(guī)范。
- 符號引用驗(yàn)證: 確保解析動(dòng)作正確執(zhí)行。
1.3、 準(zhǔn)備: 為類的靜態(tài)變量分配內(nèi)存, 并初始化為默認(rèn)值
準(zhǔn)備階段時(shí)正式為類變量分配內(nèi)存并設(shè)置初始默認(rèn)值的階段, 這些內(nèi)存都將在方法區(qū)分配。
這個(gè)階段需要注意以下幾點(diǎn):
- 這個(gè)時(shí)候僅會為類變量(static)分配內(nèi)存, 并且都在方法區(qū)中進(jìn)行, 類的實(shí)例變量在
初始化階段在堆中為其分配內(nèi)存并初始化。 - 這里設(shè)置的初始值一般一般時(shí)對應(yīng)類型的零值, 如0、 0L、 null、 false等, 而不是在Java代碼中被顯式賦予的初始值。
假設(shè)有一個(gè)類變量定義為public static int value = 3, 該變量在準(zhǔn)備階段后,其值依然為0, 而不是3, 因?yàn)檫@個(gè)時(shí)候并未執(zhí)行任何Java代碼。 - 對于基本類型來說, 對于類變量和全局變量,如果不為其顯示的賦值, 其值未默認(rèn)零值, 對于局部變量則必須使用前顯示賦值,否則編譯不通過。
- 對于
final和static同時(shí)修飾的變量, 必須在聲明時(shí)就顯示的賦值,否則編譯不通過, 對于僅僅final變量修飾的變量,可以在聲明時(shí)顯示賦值,也可以在類初始化時(shí)賦值,系統(tǒng)不會為其賦予默認(rèn)值。 - 對于reference類型, 如果不為其顯示的賦值, 則默認(rèn)值都是
null. - 對于數(shù)組類型, 如果初始化時(shí)沒有被顯示的為內(nèi)部各個(gè)元素賦值, 則會被賦予默認(rèn)零值。
1.4、 解析: 把類中的符號引用轉(zhuǎn)為直接引用
解析階段時(shí)將常量池類的符號引用轉(zhuǎn)化為直接引用。解析階段主要針對類,接口,字段,類方法,接口方法,方法符號,調(diào)用點(diǎn)等7類符號引用進(jìn)行。
- 符號引用: 符號引用就是一組符號來描述目標(biāo), 可以是任務(wù)字面量。
- 直接引用: 就是直接指向目標(biāo)的指針、相對偏移量或一個(gè)間接定位到目標(biāo)的句柄。
1.5、類的初始化
初始化主要時(shí)為類的靜態(tài)變量賦予正確初始值, JVM 負(fù)責(zé)對類初始化, 主要時(shí)對類變量初始化。 在Java中為類變量設(shè)定初始化有兩種方式:
- 在聲明時(shí)設(shè)定初始值。
- 在靜態(tài)代碼塊(
static {})中為類變量指定初始值。
JVM初始化步驟:
- 假如這個(gè)類還沒有被加載和連接, 則先進(jìn)行加載和連接。
- 假如這個(gè)類的父類還沒有被初始化, 則先初始化其父類。
- 假如類中有初始化語句, 則系統(tǒng)依次執(zhí)行這些初始化語句。
類的初始化時(shí)機(jī):
- 創(chuàng)建類的實(shí)例, 也就是new 的時(shí)候。
- 訪問類或接口的靜態(tài)變量, 或者對靜態(tài)變量賦值。
- 調(diào)用類的靜態(tài)方法。
- 反射
- 初始化某個(gè)類的子類, 其父類也會被初始化。
- Java虛擬機(jī)啟動(dòng)時(shí)被標(biāo)明為啟動(dòng)類的類, 直接用java.exe運(yùn)行某個(gè)主類。
2、類加載器
2.1、類加載器層次

啟動(dòng)類加載器(
BootStrapClassLoader),使用C++實(shí)現(xiàn), 是虛擬機(jī)自身的一部分, 負(fù)責(zé)加載存放在jdk/jre/lib下的類庫, 無法被Java程序直接引用。擴(kuò)展類加載器(
ExtClassLoader), 負(fù)責(zé)加載jdk/jre/ext下的類庫, 開發(fā)者可以直接使用擴(kuò)展類加載器。應(yīng)用類加載器(
AppClassLoader), 負(fù)責(zé)加載用戶類路徑(ClassPath)下指定的用戶類, 開發(fā)者可以直接使用應(yīng)用類加載器, 在開發(fā)著沒有指定自定義類加載器的情況下, 程序默認(rèn)的類加載器。
應(yīng)用程序由以上3類加載器相互配合加載, 用戶也可以嘗試自定義加載器, 比如:
- 嘗試從網(wǎng)絡(luò)中加載類
- 嘗試加載加密的類文件
2.2、類的加載
類的加載又種方式:
- 啟動(dòng)應(yīng)用時(shí)由JVM加載
- Class.forName()方法動(dòng)態(tài)加載
- ClassLoader.loadClass()方法動(dòng)態(tài)加載
注意事項(xiàng): - Class.forName會將類加載到JVM內(nèi)存, 并初始化執(zhí)行static塊。
- ClassLoader.loadClass僅僅將類加載到內(nèi)存,并不會初始化,只有在調(diào)用newInstance初始化實(shí)例時(shí),才會執(zhí)行static塊。
- Class.forName帶參函數(shù)可以控制是否執(zhí)行static塊。
2.3、尋找類的加載器
AppClassLoader -> ExtClassLoader -> BootStrapClassLoader
- 啟動(dòng)類加載器時(shí)所有類加載器的頂層父加載器。
- 擴(kuò)展類加載器時(shí)應(yīng)用類加載器的父加載器。
3、類的加載機(jī)制
類的加載機(jī)制有以下幾個(gè)特點(diǎn):
全盤負(fù)責(zé): 當(dāng)一個(gè)類加載器試圖加載某個(gè)類時(shí), 該類依賴和引用的其他Class都由該類加載器加載, 除非顯示使用其他類加載器加載。
父類委托: 先讓父類加載器試圖加載該類, 只有當(dāng)父類加載器無法加載該類時(shí), 才嘗試從自己的類路徑種加載該類。
緩存機(jī)制:緩存機(jī)制確保所有被加載過的類都會被緩存, 再次使用該類時(shí), 首先會嘗試從緩存區(qū)尋找該Class, 只有緩存區(qū)種找不到時(shí), 系統(tǒng)才會加載其二進(jìn)制字節(jié)碼, 并轉(zhuǎn)化為Class對象,放入緩沖區(qū)。 所以Class被修改后, 必須重啟JVM, 修改的程序才會生效。
雙親委派機(jī)制:當(dāng)一個(gè)類加載器收到類加載的請求, 首先不會自己去嘗試加載這個(gè)類, 而是委托父類加載器去完成, 依次向上, 所以所有的類加載請求最終都會傳遞給頂層的啟動(dòng)類加載器(
BootStrapClassLoader), 只有當(dāng)父類加載器無法加載時(shí), 子類加載器才會嘗試加載。
3.1、 雙親委派機(jī)制
1、 當(dāng)AppClassLoader加載一個(gè)Class時(shí), 首先會傳遞給ExtClassLoader去加載。
2、 當(dāng)ExtClassLoader收到加載Class的請求時(shí), 也不會嘗試自己去加載, 會傳遞給BootStrapClassLoader去加載。
3、 如果BootStrapClassLoader加載失敗, 則會由ExtClassLoader來加載。
4、 如果ExtClassLoader加載失敗, 會由AppClassLoader來加載。
5、 如果AppClassLoader加載失敗, 會報(bào)出異常ClassNotFoundException。
3.2、 雙親委派優(yōu)勢
- 系統(tǒng)類防止內(nèi)存中出現(xiàn)多份同樣的字節(jié)碼
- 避免重復(fù)加載類
4、 自定義類加載器
略