圖解JVM內(nèi)存模型
前言
上篇文章我們一起了解了jvm虛擬機類的加載機制,而且是以一種純大白話進(jìn)行的一場閑聊,相信小伙伴們應(yīng)該印象深刻,感興趣的小伙伴可以重溫一下上一篇文章大白話談JVM的類加載機制。
當(dāng)jvm加載了類后,會把需要使用的對象放入到內(nèi)存當(dāng)中,那么jvm的內(nèi)存模型是什么樣的呢?
今天我們就來探索一下jvm的內(nèi)存模型。由于有小伙伴反映想加些圖更容易理解,王子接下來的文章打算用更多的圖例來講解。
方法區(qū)
很多小伙伴之前也了解過jvm的內(nèi)存模型,知道有方法區(qū)這個東西,但可能了解的不是很詳細(xì)。
其實方法區(qū)是在JDK1.8以前的版本里存在的一塊內(nèi)存區(qū)域,主要就是存放從class文件里加載進(jìn)來的類的,而且常量池也是在這塊區(qū)域內(nèi)的。
但是在JDK1.8之后,這塊區(qū)域搖身一變,換了名字,叫做“Metaspace”,翻譯過來就是“元數(shù)據(jù)空間”的意思。當(dāng)然它只是改了個名,實現(xiàn)的功能是沒變的。

程序計數(shù)器
假設(shè)我們的代碼是這樣的:
public class Main {
public static void main(String[] args) {
SysUser sysUser = new SysUser();
sysUser.setAvatar("1");
}
}
這個是我們的java代碼,是面向我們開發(fā)者的,然后會編譯成class字節(jié)碼文件,在class字節(jié)碼文件中存放的是一條條的字節(jié)碼命令,他對應(yīng)了一條條的機器指令,計算機只有讀到機器指令才知道它要干什么。
所以當(dāng)JVM加載類信息后,實際上就是使用字節(jié)碼執(zhí)行引擎去執(zhí)行我們的代碼編譯出來的一條條字節(jié)碼指令,如下圖。

那么在執(zhí)行字節(jié)碼指令的時候,jvm是怎么知道該執(zhí)行哪條指令了呢?這時候程序計數(shù)器就出現(xiàn)了。
它就是用來記錄當(dāng)前執(zhí)行的字節(jié)碼指令位置的。

另外,小伙伴們都知道,JVM是支持多線程的,所以如果我們開啟了多線程,就會有多個線程在執(zhí)行不同的字節(jié)碼指令,為了他們之間的字節(jié)碼指令不會混在一起,所以每個線程都會有自己的程序計數(shù)器,用來記錄每個線程自己的指令現(xiàn)在執(zhí)行到哪一條了,如下圖:

JAVA虛擬機棧
我們現(xiàn)在知道,jvm執(zhí)行class中指令時是通過程序計數(shù)器來鎖定執(zhí)行的指令位置的,但是在我們執(zhí)行的方法里,會有很多的局部變量等數(shù)據(jù),虛擬機棧就是用來保存方法的局部變量的,而且每個線程都會有自己的虛擬機棧,比如我們之前的代碼:
public class Main {
public static void main(String[] args) {
SysUser sysUser = new SysUser();
sysUser.setAvatar("1");
}
}
這個代碼會啟動一個main線程,并把局部變量sysUser保存到棧中。
如果線程執(zhí)行了一個方法,就會對這個方法調(diào)用創(chuàng)建一個棧幀,然后就是所謂的壓棧操作(先進(jìn)后出),如下:

然后我們代碼繼續(xù)執(zhí)行,調(diào)用了setAvatar方法,那么就會繼續(xù)創(chuàng)建棧幀,如下:

當(dāng)setAcatar方法執(zhí)行完畢,就會對方法的棧幀執(zhí)行出棧操作。
以上就是JAVA虛擬機棧這一部分的作用,簡單概括就是:調(diào)用方法就創(chuàng)建棧幀,壓棧,方法執(zhí)行完就執(zhí)行出棧操作。
JAVA堆內(nèi)存
說完了java虛擬機棧,那我們再來說一個很重要的內(nèi)存區(qū)域java堆內(nèi)存,它是用來存放我們代碼中創(chuàng)建的各種對象的。
還是以剛才的代碼為例,當(dāng)我們執(zhí)行new SysUser()的時候,就創(chuàng)建了一個SysUser實例對象,而這個對象本身又會有很多的屬性和方法,這樣的實例化對象的數(shù)據(jù)就是存放在堆內(nèi)存中的。
而這個時候我們在棧中存儲的局部變量實際上存的就是這個對象的內(nèi)存地址,也可以理解為一個引用地址。如下圖:

到這里JVM的內(nèi)存區(qū)域已經(jīng)和小伙伴們介紹完了,給大家來一張整體的內(nèi)存區(qū)域圖,以便理解:

其他內(nèi)存區(qū)域
除了前文我們介紹的內(nèi)存區(qū)域,jdk的api中(io、nio、socket)相關(guān),其實它們的內(nèi)部已經(jīng)不是java代碼了,而是調(diào)用了native方法調(diào)用了本地操作系統(tǒng)的一些方法,可能是c語言編寫的或者是一些底層類庫。
在調(diào)用native方法的時候,線程就會對應(yīng)本地方法棧,這個是于java虛擬機棧類似的東東,放的就是native方法的各種局部變量表。
除此之外還有一個區(qū)域,是不屬于JVM的,通過NIO的allocateDirect的api,可以在堆外分配內(nèi)存空間,從而直接操作堆外的內(nèi)存空間數(shù)據(jù)。
有些場景下,堆外內(nèi)存空間會提升性能,這個問題我們之后再逐步探索,今天就不說這個了。
總結(jié)
本文到這里就結(jié)束啦,王子采納了一些小伙伴的意見,畫了很多圖有助于小伙伴們更好的理解,希望能夠幫到大家。
那我們下篇文章見。
以上就是圖解JVM內(nèi)存模型的詳細(xì)內(nèi)容,更多關(guān)于JVM內(nèi)存模型的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Mybatis-Plus默認(rèn)主鍵策略導(dǎo)致自動生成19位長度主鍵id的坑
這篇文章主要介紹了Mybatis-Plus默認(rèn)主鍵策略導(dǎo)致自動生成19位長度主鍵id的坑,本文一步步給大家分享解決方法,給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-12-12
java編程實現(xiàn)根據(jù)EXCEL列名求其索引的方法
這篇文章主要介紹了java編程實現(xiàn)根據(jù)EXCEL列名求其索引的方法,涉及Java元素遍歷與數(shù)學(xué)運算的相關(guān)技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-11-11
SpringBoot啟動后的初始化數(shù)據(jù)加載原理解析與實戰(zhàn)
本文主要圍繞?Spring?Boot?啟動后的初始化數(shù)據(jù)加載展開,介紹了初始化任務(wù)的基本需求,包括全局配置加載、數(shù)據(jù)庫表初始化等,闡述了多種初始化加載方式,分析了它們的優(yōu)缺點,需要的朋友可以參考下2024-11-11
MyBatis中傳入?yún)?shù)parameterType類型詳解
這篇文章主要給大家介紹了關(guān)于MyBatis中傳入?yún)?shù)parameterType類型的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2018-04-04
Springboot登錄驗證的統(tǒng)一攔截處理的實現(xiàn)
如果不進(jìn)行統(tǒng)一的攔截處理,每次用戶請求你都要去進(jìn)行用戶的信息驗證,所以本文主要介紹了Springboot登錄驗證的統(tǒng)一攔截處理的實現(xiàn),感興趣的可以了解一下,感興趣的可以了解一下2023-09-09

