Java JVM類加載機制解讀
1.什么是類加載
首先你要知道一個類的從被加載到虛擬機內(nèi)存中開始,到被初始化為止,是為類加載的整個過程。下圖就是類加載的整個過程:

一個類只有經(jīng)歷了加載、驗證、準備、解析、初始化這五個關卡才能被認為是實現(xiàn)了類加載。這,就是類加載。
注意一點:上面五個過程并不是按部就班地“完成”,而是按部就班地“執(zhí)行”(除解析過程外)。執(zhí)行時一定是先開始加載,再開始驗證,但加載過程中也可能會直接開始驗證。
2.類加載的過程
2.1加載
“加載”只是是“類加載”過程的第一個階段,關于在什么時候開始,規(guī)范并沒有進行強制約束,可以讓虛擬機自行把握。在這個階段中,Java虛擬機需要完成以下三件事:
1)通過一個類的全限定名來獲取這個類的二進制字節(jié)流
2)將這個字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運行時數(shù)據(jù)結(jié)構(gòu)
3)在內(nèi)存中生成一個代表這個類的java.lang.Class對象,作為方法區(qū)這個類的各種數(shù)據(jù)的方問入口
可以用一句話概括:加載是一個讀取Class文件,將其轉(zhuǎn)化為某種靜態(tài)數(shù)據(jù)結(jié)構(gòu)存儲在方法區(qū)內(nèi),并在堆中生成一個便于用戶調(diào)用的java.lang.Class類型的對象的過程

2.2驗證
驗證是連接階段的第一步,這個階段的目的是確保Class文件的字節(jié)流中包含的信息符合約束要求,,保證這些信息被當做代碼運行后不會危害虛擬機自身的安全。
這一過程了解即可。
2.3準備
準備階段是正式為類中定義的變量(這里說的是靜態(tài)變量,也就是被static修飾的變量)分配內(nèi)存,并設置類變量初始值的階段。
這里有兩點需要強調(diào):
1)首先這里進行內(nèi)存分配的僅僅是類變量,而不包括實例變量,實例變量將會在對象實例化時隨著對象一起分配在Java堆中。
2)其次這里設置的初始值“通常情況”下是數(shù)據(jù)的零值,而不是用戶本身對它賦的初值。
如下代碼:
public static int a = 10;
變量a在準備階段后的初始值是0,而不是10,因為現(xiàn)在只是在類加載過程中,還沒有執(zhí)行任何方法。
上面說到“通常情況”,那就說明還有特殊情況咯,加修飾詞final時:
public static final int a = 10;
這時在準備階段虛擬機就會將a設置為10。其實也不難理解:我們將它設置為常量,那就肯定在任何時候都不能修改啊,天子犯法與庶民同罪!
2.4解析
解析階段是Java虛擬機將常量池內(nèi)的符號引用替換為直接引用的過程,這一過程也可能在初始化后進行,并不一定和流程圖的執(zhí)行順序一樣。
符號引用:符號引用以一組符號來描述所引用的目標,符號可以是任何形式的字面量,只要使用時能無歧義地定位到目標即可。
直接引用:直接引用是可以直接指向目標的指針、相對偏移量或者是一個能間接定位到目標的句柄。
這一過程比較復雜,有興趣可以參考《深入理解Java虛擬機》
2.5初始化【重中之重之重中重】
類的初始化階段是類加載過程的最后一個階段。在這個階段Java虛擬機才開始真正執(zhí)行類中編寫的Java程序代碼。
初始化階段有以下六種情況必須立即對類進行“初始化”:
- 1)使用new關鍵字實例化對象的時候
- 2)讀取或設置一個類的靜態(tài)字段(被final修飾、已在編譯期把結(jié)果放入常量池的靜態(tài)字段除外)的時候
- 3)調(diào)用一個類的靜態(tài)方法的時候
- 4)使用java.lang.reflect包的方法對類進行反射調(diào)用的時候,如果類沒有進行過初始化,則需要先觸發(fā)其初始化。
- 5)當初始化一個類的時候,如果發(fā)現(xiàn)其父類還沒有進行過初始化,則需要先觸發(fā)其父類的初始化。
- 6)當虛擬機啟動時,用戶需要指定一個要執(zhí)行的主類(包含main()方法的那個類),虛擬機會先初始化這個主類。
光說不行,主要看
第一段代碼:
package com.bit.JVMTest;
class Father {
public static int a = 10;
static {
System.out.println("爸爸靜態(tài)代碼塊");
}
}
class Son extends Father{
public static int b = 20;
static {
System.out.println("兒子靜態(tài)代碼塊");
}
}
public class ClassLoaderTest {
public static void main(String[] args) {
System.out.println(Son.b);
}
}
運行結(jié)果:
爸爸靜態(tài)代碼塊
兒子靜態(tài)代碼塊
20
首先Son.b是在讀取Son類自己的靜態(tài)字段,這點符合上面六中情況的第二種:讀取或設置一個類的靜態(tài)字段(被final修飾、已在編譯期把結(jié)果放入常量池的靜態(tài)字段除外)的時候需要進行初始化。
其次Son類繼承Father類,也就符合第五條:當初始化一個類的時候,如果發(fā)現(xiàn)其父類還沒有進行過初始化,則需要先觸發(fā)其父類的初始化,所以我們先初始化的應該是Father類,然后是Son類。
因此,打印的內(nèi)容首先是爸爸靜態(tài)代碼塊(父類先初始化),然后是兒子靜態(tài)代碼塊(子類再初始化),最后是我們想要打印的b(20)本身。
再看
第二段代碼:
package com.bit.JVMTest;
class grandFather{
static{
System.out.println("爺爺靜態(tài)代碼塊");
}
}
class Father extends grandFather{
public static int a = 10;
static {
System.out.println("爸爸靜態(tài)代碼塊");
}
}
class Son extends Father{
public static int b = 20;
static {
System.out.println("兒子靜態(tài)代碼塊");
}
}
public class ClassLoaderTest {
public static void main(String[] args) {
System.out.println(Son.a);
}
}
運行結(jié)果:
爺爺靜態(tài)代碼塊
爸爸靜態(tài)代碼塊
10
首先要明確:Son.a是在讀取父類Father類的靜態(tài)字段(注意a字段在Son類的父類中),而不是讀取Son類本身的靜態(tài)字段
因此這次不會初始化Son類本身。
因此這次不會初始化Son類本身。
因此這次不會初始化Son類本身。
其它的和第一段代碼很相似:JVM在初始化Father類的時候,發(fā)現(xiàn)這個類還有一個父類沒有被初始化,那就先初始化它的父類:grandFather。
因此,打印的內(nèi)容首先是爺爺靜態(tài)代碼塊(Father類的父類先初始化),然后是爸爸靜態(tài)代碼塊(Father類再初始化),最后是我們想要打印的a(10)本身。
第三段代碼:
package com.bit.JVMTest;
class grandFather{
static{
System.out.println("爺爺靜態(tài)代碼塊");
}
}
class Father extends grandFather{
public final static int a = 10;
static {
System.out.println("爸爸靜態(tài)代碼塊");
}
}
class Son extends Father{
public static int b = 20;
static {
System.out.println("兒子靜態(tài)代碼塊");
}
}
public class ClassLoaderTest {
public static void main(String[] args) {
System.out.println(Son.a);
}
}
運行結(jié)果:10
看到這里是不是想說臥**你*個*。
別急別急,這里的主函數(shù)調(diào)用雖然和第二段代碼一樣,但是注意!?。∥覀兘oa這個靜態(tài)字段加了一個final修飾符。
再看六條中的第(2)條:讀取或設置一個類的靜態(tài)字段(被final修飾、已在編譯期把結(jié)果放入常量池的靜態(tài)字段除外)的時候會觸發(fā)類加載。
也就是說我們讀取的a是被final修飾的,讀取這種靜態(tài)字段并不會引起任何類的初始化,所以就直接打印a(10)了。
再看
最后一段代碼:
package com.bit.JVMTest;
class Father {
public Father(){
System.out.println("爸爸構(gòu)造方法");
}
static {
System.out.println("爸爸靜態(tài)代碼塊");
}
{
System.out.println("爸爸普通代碼塊");
}
}
class Son extends Father{
public Son(){
System.out.println("兒子構(gòu)造方法");
}
static {
System.out.println("兒子靜態(tài)代碼塊");
}
{
System.out.println("兒子普通代碼塊");
}
}
public class ClassLoaderTest extends Son{
public static void main(String[] args) {
System.out.println("開始");
new Son();//這里實例化一個Son類的對象
System.out.println("結(jié)束");
}
}
運行結(jié)果:
爸爸靜態(tài)代碼塊
兒子靜態(tài)代碼塊
開始
爸爸普通代碼塊
爸爸構(gòu)造方法
兒子普通代碼塊
兒子構(gòu)造方法
結(jié)束
看到這里是不是欲哭無淚,我**不學了我。別急先聽我細細分析一波~
這里有一個細節(jié):主類繼承了Son類!,這貌似沒什么啊,但是還有一個細節(jié):我們的main()方法是主類中的靜態(tài)方法!看到這里是不是明白了些什么?
沒錯!當我們調(diào)用main()方法的時候,就引起了主類的初始化,主類繼承Son類,Son類繼承Father類,所以就先進行Father類的初始化:打印爸爸靜態(tài)代碼塊,接著Son類初始化:打印兒子靜態(tài)代碼塊,最后該終于我主類初始化了:代碼中沒什么可以初始化的…(尷尬)。
接下來是第二階段:執(zhí)行main()方法:
1.先打?。洪_始字樣。
2.接著是構(gòu)造 Son()實例,那么就會先構(gòu)造它的父類Father()的實例:構(gòu)造實例時按照先執(zhí)行代碼塊,再執(zhí)行構(gòu)造方法的順序來。所以就先打印了:爸爸普通代碼塊、爸爸構(gòu)造方法 這幾個大字。然后再執(zhí)行構(gòu)造Son()的實例,構(gòu)造順序一樣,所以就后打印了:兒子普通代碼塊、兒子構(gòu)造方法 這幾個大字。
3.最后打?。航Y(jié)束字樣。
此時main()才方法真正結(jié)束。
總結(jié)
我們平常所說的類加載體現(xiàn)在代碼上就是初始化這一階段,我這里結(jié)束的也僅限于此,想了解詳細的類加載可以參考《深入理解Java虛擬機》這本書,也可以看其他博主的知識總結(jié)。感謝你能看到這里!
到此這篇關于Java JVM類加載機制解讀的文章就介紹到這了,更多相關Java JVM 類加載機制內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Spring事務傳播中嵌套調(diào)用實現(xiàn)方法詳細介紹
Spring事務的本質(zhì)就是對數(shù)據(jù)庫事務的支持,沒有數(shù)據(jù)庫事務,Spring是無法提供事務功能的。Spring只提供統(tǒng)一的事務管理接口,具體實現(xiàn)都是由數(shù)據(jù)庫自己實現(xiàn)的,Spring會在事務開始時,根據(jù)當前設置的隔離級別,調(diào)整數(shù)據(jù)庫的隔離級別,由此保持一致2022-11-11
在java8中使用流區(qū)分質(zhì)數(shù)與非質(zhì)數(shù)詳解
這篇文章主要介紹了在java8中使用流區(qū)分質(zhì)數(shù)與非質(zhì)數(shù)詳解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-12-12
如何將Spring Session存儲到Redis中實現(xiàn)持久化
這篇文章主要介紹了如何將Spring Session存儲到Redis中實現(xiàn)持久化,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-07-07
Java下載文件中文文件名亂碼的解決方案(文件名包含很多%)
Java下載文件時,文件名中文亂碼問題通常是由于編碼不正確導致的,使用`URLEncoder.encode(filepath, "UTF-8")`可以解決在提示下載框中正確顯示漢字文件名的問題,但在選擇直接打開時,文件名會變成亂碼,解決這個問題的方法2025-02-02
java并發(fā)編程之深入理解Synchronized的使用
文詳細講述了線程、進程的關系及在操作系統(tǒng)中的表現(xiàn),這是多線程學習必須了解的基礎。本文將接著講一下Java線程同步中的一個重要的概念synchronized,希望能夠給你有所幫助2021-06-06
SpringBoot整合mybatis-generator插件流程詳細講解
這篇文章主要介紹了SpringBoot整合mybatis-generator插件流程,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習吧2023-02-02
SpringBoot靜態(tài)視頻實時播放的實現(xiàn)代碼
這篇文章主要介紹了SpringBoot靜態(tài)視頻實時播放的實現(xiàn)代碼,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-01-01

