分析JVM的組成結(jié)構(gòu)
一、JavaSE體系
- JavaSE,Java 平臺(tái)標(biāo)準(zhǔn)版,為 Java EE 和 Java ME 提供了基礎(chǔ)。
- JDK:Java 開發(fā)工具包,JDK 是 JRE 的超集,包含 JRE 中的所有內(nèi)容,以及開發(fā)程序所需的編譯器和調(diào)試程序等工具。
- JRE:Java SE 運(yùn)行時(shí)環(huán)境 ,提供庫(kù)、Java 虛擬機(jī)和其他組件來(lái)運(yùn)行用 Java 編程語(yǔ)言編寫的程序。主要類庫(kù),包括:程序部署發(fā)布、用戶界面工具類、繼承庫(kù)、其他基礎(chǔ)庫(kù),語(yǔ)言和工具基礎(chǔ)庫(kù)。
- JVM:Java 虛擬機(jī),負(fù)責(zé) JavaSE 平臺(tái)的硬件和操作系統(tǒng)無(wú)關(guān)性、編譯執(zhí)行代碼(字節(jié)碼)和平臺(tái)安全性。
二、運(yùn)行時(shí)數(shù)據(jù)區(qū)
- 線程私有:程序計(jì)數(shù)器、虛擬機(jī)棧、本地方法棧。
- 線程共享:堆、方法區(qū)。

三、程序計(jì)數(shù)器
3.1、什么是程序計(jì)數(shù)器
程序計(jì)數(shù)器是一塊較小的內(nèi)存空間,它的作用可以看作是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器。在虛擬機(jī)的概念模型里字節(jié)碼解釋器工作時(shí)就是通過(guò)改變這個(gè)計(jì)數(shù)器的值來(lái)選取下一條需要執(zhí)行的字節(jié)碼指令,分支、循環(huán)、跳轉(zhuǎn)、異常處理、線程恢復(fù)等基礎(chǔ)功能都需要依賴這個(gè)計(jì)數(shù)器來(lái)完成。 -- 摘自《深入理解Java虛擬機(jī)》
3.2、程序計(jì)數(shù)器有什么特點(diǎn)
- 程序計(jì)數(shù)器會(huì)隨著線程的啟動(dòng)而創(chuàng)建,各線程之間獨(dú)立存儲(chǔ),互不影響。
- 當(dāng)前線程執(zhí)行的字節(jié)碼的行號(hào)指示器。
- 如果線程正在執(zhí)行的是一個(gè) Java 方法,則指明當(dāng)前線程執(zhí)行的代字節(jié)碼行數(shù)。
- 如果正在執(zhí)行的是 Natvie 方法(本地方法),這個(gè)計(jì)數(shù)器值則為空(Undefined)。
- 占用較小的內(nèi)存空間,此內(nèi)存區(qū)域是唯一一個(gè)不會(huì)出現(xiàn) OutOfMemoryError(內(nèi)存溢出) 情況的區(qū)域。
3.3、用個(gè)例子來(lái)說(shuō)明
請(qǐng)無(wú)視我文章中取得類名,為了方便實(shí)驗(yàn)演示,命名怎么快怎么來(lái)。
public class Jvm1 {
public int test(){
int a = 100;
int b = 200;
return a + b;
}
}
這樣一個(gè)類, javac Jvm1.java,編譯成Jvm1.class文件。
再使用 javap 反匯編工具javap -c Jvm1.class看下.class文件中數(shù)據(jù)格式。

這個(gè)就是前面提到的 當(dāng)前線程執(zhí)行的字節(jié)碼的行號(hào),而程序計(jì)數(shù)器則記錄的這個(gè)數(shù)字。
- 當(dāng)然這也解釋了程序計(jì)數(shù)器不存在
OutOfMemoryError的原因,因?yàn)樗涗浀闹皇菙?shù)字,占用空間少。 - 同時(shí)也解釋了為什么執(zhí)行的是一個(gè) Java 方法時(shí),則指明當(dāng)前線程執(zhí)行的代字節(jié)碼行數(shù)。
- 而執(zhí)行 native方法 時(shí)程序計(jì)數(shù)器為 Undefined,因?yàn)?native方法 是大多是通過(guò)C實(shí)現(xiàn)并未編譯成需要執(zhí)行的字節(jié)碼指令,所以在計(jì)數(shù)器中當(dāng)然是空。
四、虛擬機(jī)棧
- 棧有什么特點(diǎn)? 先進(jìn)后出。
- 虛擬機(jī)棧是每個(gè)線程私有的,線程在運(yùn)行時(shí),在執(zhí)行每個(gè)方法的時(shí)候都會(huì)打包成一個(gè) 棧幀,存儲(chǔ)了 局部變量表,操作數(shù)據(jù)棧,動(dòng)態(tài)鏈接,方法出口等信息,然后放入棧。每個(gè)時(shí)刻正在執(zhí)行的當(dāng)前方法就是虛擬機(jī)棧頂?shù)臈E。方法的執(zhí)行就對(duì)應(yīng)著棧幀在虛擬機(jī)棧中入棧和出棧的過(guò)程。
- 棧的大小缺省為 1M,可用參數(shù) –Xss 調(diào)整大小,例如-Xss256k。
一個(gè)例子來(lái)看看執(zhí)行 每個(gè)方法入棧出棧 的過(guò)程。
public class Jvm2 {
public static void main(String[] args) {
A();
}
public static void A() {
System.out.println("A開始");
// 此處省略100行代碼
B(); // 調(diào)用B方法
System.out.println("A結(jié)束");
}
public static void B() {
System.out.println("B開始");
// 此處省略100行代碼
C(); // 調(diào)用B方法
System.out.println("B結(jié)束");
}
public static void C() {
System.out.println("C開始");
// 此處省略100行代碼
System.out.println("C結(jié)束");
}
}
輸出:
A開始
B開始
C開始
C結(jié)束
B結(jié)束
A結(jié)束
4.1、局部變量表
- 顧名思義就是局部變量的表,用于存放我們的局部變量的。
- 主要存放我們的 Java 的八大基礎(chǔ)數(shù)據(jù)類型,如果是局部的一些對(duì)象,比如我們的 Object 對(duì)象,我們只需要存放它的一個(gè)引用地址即可。(基本數(shù)據(jù)類型、對(duì)象引用、returnAddress 類型)。
4.2、操作數(shù)據(jù)棧
- 存放我們方法執(zhí)行的操作數(shù)的,它就是一個(gè)棧,先進(jìn)后出的棧結(jié)構(gòu)。
- 操作數(shù)棧,就是用來(lái)操作的,操作的的元素可以是任意的 java 數(shù)據(jù)類型。
- 所以我們知道一個(gè)方法剛剛開始的時(shí)候,這個(gè)方法的操作數(shù)棧就是空的,操作數(shù)棧運(yùn)行方法是會(huì)一直運(yùn)行入棧/出棧的操作。
數(shù)據(jù)重疊優(yōu)化
虛擬機(jī)概念模型中每二個(gè)棧幀都是相互獨(dú)立的,但在實(shí)際應(yīng)用是我們知道一個(gè)方法調(diào)用另一個(gè)方法時(shí),往往存在參數(shù)傳遞,這種做法在虛擬機(jī)實(shí)現(xiàn)過(guò)程中會(huì)做一些優(yōu)化,具體做法如下:令兩個(gè)棧幀出現(xiàn)一部分重疊。讓下面棧幀的一部分操作數(shù)棧與上面棧幀的部分局部變量表重疊在一起,進(jìn)行方法調(diào)用時(shí)就可以共用一部分?jǐn)?shù)據(jù),無(wú)須進(jìn)行額外的參數(shù)復(fù)制傳遞。

4.3、動(dòng)態(tài)鏈接
需要類加載、運(yùn)行時(shí)才能確定具體的方法。
棧幀中會(huì)持有一個(gè)引用(符號(hào)引用),該引用指向某個(gè)具體方法。
符號(hào)引用是一個(gè)地址位置的代號(hào),在編譯的時(shí)候我們是不知道某個(gè)方法在運(yùn)行的時(shí)候是放到哪里的,這時(shí)我用代號(hào) com/enjoy/pojo/User.Say:()V 指代某個(gè)類的方法,將來(lái)可以把符號(hào)引用轉(zhuǎn)換成直接引用進(jìn)行真實(shí)的調(diào)用。用符號(hào)引用轉(zhuǎn)化成直接引用的解析時(shí)機(jī),把解析分為兩大類:
- 靜態(tài)解析:符號(hào)引用在類加載階段或者第一次使用的時(shí)候就直接轉(zhuǎn)換成直接引用。
- 動(dòng)態(tài)連接:符號(hào)引用在每次運(yùn)行期間轉(zhuǎn)換為直接引用,即每次運(yùn)行都重新轉(zhuǎn)換。
4.4、方法出口
1、正常返回(調(diào)用程序計(jì)數(shù)器中的地址作為返回)三步曲
- 恢復(fù)上層方法的局部變量表和操作數(shù)棧
- 把返回值(如果有的話)壓入調(diào)用者棧幀的操作數(shù)棧中
- 調(diào)整 PC 計(jì)數(shù)器的值以指向方法調(diào)用指令后面的一條指令
2、異常返回
指方法執(zhí)行過(guò)程中遇到異常,并且這個(gè)異常在方法體內(nèi)部沒有得到處理,導(dǎo)致方法退出
4.5、棧溢出
- java.lang.StackOverflowError:一般的方法調(diào)用是很難出現(xiàn)的,如果出現(xiàn)了要考慮是否有 無(wú)限遞歸 ,虛擬機(jī)棧帶給我們的啟示:方法的執(zhí)行因?yàn)橐虬蓷E,所以天生要比實(shí)現(xiàn)同樣功能的循環(huán)慢,所以樹的遍歷算法中:遞歸和非遞歸(循環(huán)來(lái)實(shí)現(xiàn))都有存在的意義。遞歸代碼簡(jiǎn)潔,非遞歸代碼復(fù)雜但是速度較快。
- OutOfMemoryError:不斷建立線程(一般演示不出,演示出來(lái)機(jī)器也死了)。
五、本地方法棧
- 本地方法棧和虛擬機(jī)棧所發(fā)揮的作用是非常相似的,其區(qū)別不過(guò)是虛擬機(jī)棧為虛擬機(jī)執(zhí)行 Java 方法(也就是字節(jié)碼)服務(wù),而本地方法棧則是為虛擬機(jī)使用到的 Native 方法服務(wù)。
- 虛擬機(jī)規(guī)范中對(duì)本地方法棧中的方法使用的語(yǔ)言、使用方式與數(shù)據(jù)結(jié)構(gòu)并沒有強(qiáng)制規(guī)定,因此具體的虛擬機(jī)可以自由實(shí)現(xiàn)它。
- 本地方法棧 native 方法通過(guò) JNI 調(diào)用到了底層的 C/C++(c/c++可以觸發(fā)匯編語(yǔ)言,然后驅(qū)動(dòng)硬件)。
- 當(dāng)一個(gè)JVM創(chuàng)建的線程調(diào)用native方法后,JVM不再為其在虛擬機(jī)棧中創(chuàng)建棧幀,JVM只是簡(jiǎn)單地動(dòng)態(tài)鏈接并直接調(diào)用native方法。
六、方法區(qū)
主要存儲(chǔ)類信息、常量池、靜態(tài)變量、即時(shí)編譯期編譯后的代碼等數(shù)據(jù)。
永久代和元空間:
方法區(qū)在 jdk1.7 及其之前又背稱為永久代,jdk1.8 又被稱為元空間,怎么理解呢?
1.jdk1.8移除了永久代,新增了元空間。
2.可以理解為方法區(qū)是一個(gè)規(guī)范,但是具體怎么實(shí)現(xiàn)要看具體的jvm怎么實(shí)現(xiàn)。
3.就類似于提供了一個(gè)接口方法(規(guī)范),只要實(shí)現(xiàn)了這個(gè)接口的類,那么就要去實(shí)現(xiàn)里面接口方法(具體實(shí)現(xiàn)就是各種版本jvm之間和版本之間的差異了)。
4.各種版本jvm 。
- HotSpot VM(SUN) 以前使用范圍最廣的Java虛擬機(jī)。
- JRockit VM(BEA) 號(hào)稱世界上最快的JVM 。
- Dalvik VM(Google) google自己開發(fā)的。
- HotSPont VM(ORACLE) 目前以前使用范圍最廣的Java虛擬機(jī)。
5.版本差異(jdk1.7, jdk1.8) 。
參數(shù)設(shè)置:
- jdk1.7 及以前:-XX:PermSize;-XX:MaxPermSize;
- jdk1.8 以后:-XX:MetaspaceSize; -XX:MaxMetaspaceSize
- jdk1.8 以后大小就只受本機(jī)總內(nèi)存的限制
七、堆
- 幾乎所有對(duì)象都分配在堆內(nèi)存,也是垃圾回收發(fā)生的主要區(qū)域。
- 堆內(nèi)存由多個(gè)線程共享。堆內(nèi)存隨著JVM啟動(dòng)而創(chuàng)建。
參數(shù)設(shè)置:
-Xms:堆的最小值
-Xmx:堆的最大值
-Xmn:新生代的大小
-XX:NewSize;新生代最小值
-XX:MaxNewSize:新生代最大值
八、運(yùn)行時(shí)常量池
8.1、符號(hào)引用
- 一個(gè) java 類(假設(shè)為 People 類)被編譯成一個(gè) class 文件時(shí),如果 People 類引用了 Tool 類,但是在編譯時(shí) People 類并不知道引用類的實(shí)際內(nèi)存地址,因此只能使用符號(hào)引用來(lái)代替。
- 而在類裝載器裝載 People 類時(shí),此時(shí)可以通過(guò)虛擬機(jī)獲取 Tool 類的實(shí)際內(nèi)存地址,因此便可以既將符號(hào) org.simple.Tool 替換為 Tool 類的實(shí)際內(nèi)存地址,及直接引用地址。
- 即在編譯時(shí)用符號(hào)引用來(lái)代替引用類,在加載時(shí)再通過(guò)虛擬機(jī)獲取該引用類的實(shí)際地址。
- 以一組符號(hào)來(lái)描述所引用的目標(biāo),符號(hào)可以是任何形式的字面量,只要使用時(shí)能無(wú)歧義地定位到目標(biāo)即可。符號(hào)引用與虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局是無(wú)關(guān)的,引用的目標(biāo)不一定已經(jīng)加載到內(nèi)存中。
8.2、字面量
- 文本字符串 String a = "abc",這個(gè) abc 就是字面量。
- 八種基本類型 int a = 1; 這個(gè) 1 就是字面量。
- 聲明為 final 的常量。
8.3、jvm各版本運(yùn)行時(shí)常量池變化
- 運(yùn)行時(shí)常量池:Class 文件中的常量池(編譯器生成的各種字面量和符號(hào)引用)會(huì)在類加載后被放入這個(gè)區(qū)域。
- JDK1.6:運(yùn)行時(shí)常量池在方法區(qū)(永久代)中。
- JDK1.7:運(yùn)行時(shí)常量池在堆中。
- JDK1.8:去永久代:使用元空間(空間大小只受制于機(jī)器的內(nèi)存)替代永久代。
8.4、直接內(nèi)存
內(nèi)存對(duì)象分配在JVM中堆以外的內(nèi)存,也可以稱為直接內(nèi)存,這些內(nèi)存直接受操作系統(tǒng)管理(而不是JVM),這樣做的好處是能夠在一定程度上減少垃圾回收對(duì)應(yīng)用程序造成的影響。
- 使用 Native 函數(shù)庫(kù)直接分配堆外內(nèi)存(NIO)。
- 并不是 JVM 運(yùn)行時(shí)數(shù)據(jù)區(qū)域的一部分,但是會(huì)被頻繁使用(可以通過(guò)-XX:MaxDirectMemorySize 來(lái)設(shè)置(默認(rèn)與堆內(nèi)存最大值一樣,也會(huì)出現(xiàn) OOM 異常)。
- 避免了在 Java 堆和 Native 堆中來(lái)回復(fù)制數(shù)據(jù),能夠提高效率。
以上就是分析JVM的組成結(jié)構(gòu)的詳細(xì)內(nèi)容,更多關(guān)于JVM組成結(jié)構(gòu)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringMVC項(xiàng)目異常處理機(jī)制詳解
SpringMVC是一種基于Java,實(shí)現(xiàn)了Web MVC設(shè)計(jì)模式,請(qǐng)求驅(qū)動(dòng)類型的輕量級(jí)Web框架,即使用了MVC架構(gòu)模式的思想,將Web層進(jìn)行職責(zé)解耦?;谡?qǐng)求驅(qū)動(dòng)指的就是使用請(qǐng)求-響應(yīng)模型,框架的目的就是幫助我們簡(jiǎn)化開發(fā),SpringMVC也是要簡(jiǎn)化我們?nèi)粘eb開發(fā)2022-08-08
下載遠(yuǎn)程maven倉(cāng)庫(kù)的jar?手動(dòng)放到本地倉(cāng)庫(kù)詳細(xì)操作
這篇文章主要介紹了如何下載遠(yuǎn)程maven倉(cāng)庫(kù)的jar?手動(dòng)放到本地倉(cāng)庫(kù),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-03-03
JAVA實(shí)現(xiàn)Excel和PDF上下標(biāo)的操作代碼
這篇文章主要介紹了JAVA實(shí)現(xiàn)Excel和PDF上下標(biāo),本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-09-09
一文掌握SpringSecurity?BCrypt密碼加密和解密
BCrypt就是一款加密工具,可以比較方便地實(shí)現(xiàn)數(shù)據(jù)的加密工作。也可以簡(jiǎn)單理解為它內(nèi)部自己實(shí)現(xiàn)了隨機(jī)加鹽處理,這篇文章主要介紹了SpringSecurity?BCrypt密碼加密和解密,一文學(xué)會(huì)使用BCryptPasswordEncoder的方法,需要的朋友可以參考下2023-04-04
Java攔截器Interceptor和過(guò)濾器Filte的執(zhí)行順序和區(qū)別
本文主要介紹了Java攔截器Interceptor和過(guò)濾器Filte的執(zhí)行順序和區(qū)別,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08
springboot統(tǒng)一異常處理(返回json)并格式化異常
這篇文章主要介紹了springboot統(tǒng)一異常處理(返回json)并格式化異常,對(duì)spring boot的默認(rèn)異常處理方式進(jìn)行修改,要統(tǒng)一返回?cái)?shù)據(jù)格式,優(yōu)雅的數(shù)據(jù)交互,優(yōu)雅的開發(fā)應(yīng)用,需要的朋友可以參考下2023-07-07
一次排查@CacheEvict注解失效的經(jīng)歷及解決
這篇文章主要介紹了一次排查@CacheEvict注解失效的經(jīng)歷及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12
Java后端學(xué)習(xí)精華之TCP通信傳輸協(xié)議詳解
TCP/IP是一種面向連接的、可靠的、基于字節(jié)流的傳輸層通信協(xié)議,它會(huì)保證數(shù)據(jù)不丟包、不亂序。TCP全名是Transmission Control Protocol,它是位于網(wǎng)絡(luò)OSI模型中的第四層2021-09-09

