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

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

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

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

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

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

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

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

其他內(nèi)存區(qū)域
除了前文我們介紹的內(nèi)存區(qū)域,jdk的api中(io、nio、socket)相關(guān),其實(shí)它們的內(nèi)部已經(jīng)不是java代碼了,而是調(diào)用了native方法調(diào)用了本地操作系統(tǒng)的一些方法,可能是c語(yǔ)言編寫(xiě)的或者是一些底層類(lèi)庫(kù)。
在調(diào)用native方法的時(shí)候,線(xiàn)程就會(huì)對(duì)應(yīng)本地方法棧,這個(gè)是于java虛擬機(jī)棧類(lèi)似的東東,放的就是native方法的各種局部變量表。
除此之外還有一個(gè)區(qū)域,是不屬于JVM的,通過(guò)NIO的allocateDirect的api,可以在堆外分配內(nèi)存空間,從而直接操作堆外的內(nèi)存空間數(shù)據(jù)。
有些場(chǎng)景下,堆外內(nèi)存空間會(huì)提升性能,這個(gè)問(wèn)題我們之后再逐步探索,今天就不說(shuō)這個(gè)了。
總結(jié)
本文到這里就結(jié)束啦,王子采納了一些小伙伴的意見(jiàn),畫(huà)了很多圖有助于小伙伴們更好的理解,希望能夠幫到大家。
那我們下篇文章見(jiàn)。
以上就是圖解JVM內(nèi)存模型的詳細(xì)內(nèi)容,更多關(guān)于JVM內(nèi)存模型的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Druid連接池未關(guān)閉導(dǎo)致內(nèi)存泄漏問(wèn)題
這篇文章主要介紹了Druid連接池未關(guān)閉導(dǎo)致內(nèi)存泄漏問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12
解決SpringBoot文件上傳臨時(shí)目錄找不到的問(wèn)題
這篇文章主要介紹了解決SpringBoot文件上傳臨時(shí)目錄找不到的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07
解決springboot mapper注入報(bào)紅問(wèn)題
這篇文章主要介紹了解決springboot mapper注入報(bào)紅問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11
如何解決java:找不到符號(hào)符號(hào):類(lèi)__(使用了lombok的注解)
在使用IntelliJ IDEA開(kāi)發(fā)Java項(xiàng)目時(shí),可能遇到通過(guò)@lombok注解自動(dòng)生成get和set方法不生效的問(wèn)題,解決這一問(wèn)題需要幾個(gè)步驟,首先,確認(rèn)Lombok插件已在IDEA中安裝并啟用,其次,確保項(xiàng)目中已添加Lombok的依賴(lài),對(duì)于Maven和Gradle項(xiàng)目2024-10-10
Spring Boot中如何使用Convert接口實(shí)現(xiàn)類(lèi)型轉(zhuǎn)換器
這篇文章主要介紹了Spring Boot中使用Convert接口實(shí)現(xiàn)類(lèi)型轉(zhuǎn)換器的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08
Lombok注解之@SuperBuilder--解決無(wú)法builder父類(lèi)屬性問(wèn)題
這篇文章主要介紹了Lombok注解之@SuperBuilder--解決無(wú)法builder父類(lèi)屬性問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-09-09
詳解如何判斷Java線(xiàn)程池任務(wù)已執(zhí)行完
線(xiàn)程池的使用并不復(fù)雜,麻煩的是如何判斷線(xiàn)程池中的任務(wù)已經(jīng)全部執(zhí)行完了,所以接下來(lái),我們就來(lái)看看如何判斷線(xiàn)程中的任務(wù)是否已經(jīng)全部執(zhí)行完吧2023-08-08
SpringBoot通過(guò)Nginx代理獲取真實(shí)IP
springboot作為后臺(tái)代碼,獲取到的登錄IP是前臺(tái)的代理服務(wù)器地址,并不是用戶(hù)的真實(shí)IP地址,本文主要介紹了SpringBoot通過(guò)Nginx代理獲取真實(shí)IP,具有一定的參考價(jià)值,感興趣的可以了解一下2024-01-01
Java super關(guān)鍵字用法實(shí)戰(zhàn)案例分析
這篇文章主要介紹了Java super關(guān)鍵字用法,結(jié)合具體案例形式分析了java super關(guān)鍵字調(diào)用父類(lèi)構(gòu)造方法、屬性及方法等相關(guān)操作技巧與注意事項(xiàng),需要的朋友可以參考下2019-09-09

