日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪(fǎng)問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

jvm内存结构_聊聊JVM内存结构

發(fā)布時(shí)間:2025/3/15 编程问答 12 豆豆
生活随笔 收集整理的這篇文章主要介紹了 jvm内存结构_聊聊JVM内存结构 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

起因

我們經(jīng)常會(huì)在面試的時(shí)候被問(wèn)到JVM的內(nèi)存結(jié)構(gòu),很多人會(huì)覺(jué)得這東西真的有用嗎?也就是面試造火箭,入職擰螺絲。問(wèn)這個(gè)就是純粹來(lái)刁難人的吧。

但實(shí)際上,我們細(xì)想一下。

?假設(shè)你不知道局部變量實(shí)際上屬于線(xiàn)程棧中的而非堆里面的,那么你就可能會(huì)擔(dān)心是不是我這樣寫(xiě)會(huì)有并發(fā)問(wèn)題;?假設(shè)你不知道對(duì)象實(shí)例是分配在堆中,屬于JVM共用的,那么你根本也就不會(huì)考慮在進(jìn)行某些訪(fǎng)問(wèn)時(shí)進(jìn)行加鎖;?還有很多高并發(fā)的問(wèn)題,比如volatile的原理,如果不知道JMM,怎么知道volatile的實(shí)現(xiàn)原理呢;... 這一系列的問(wèn)題,如果你對(duì)JVM的內(nèi)存結(jié)構(gòu)不了解的話(huà),都很難去回答這些問(wèn)題。

內(nèi)存結(jié)構(gòu)是什么

JVM的內(nèi)存結(jié)構(gòu)是JVM底層內(nèi)存管理的一個(gè)虛擬架構(gòu),你可以對(duì)比理解為操作系統(tǒng)中的內(nèi)存模型,而對(duì)于JVM來(lái)說(shuō),JMM就相當(dāng)于是它的內(nèi)存管理模型。包括高并發(fā)等都依賴(lài)了它,并且還依賴(lài)后續(xù)的JMM(Java Memory Model)——JVM的內(nèi)存模型

內(nèi)存結(jié)構(gòu)長(zhǎng)什么樣

既然內(nèi)存結(jié)構(gòu)這么重要,那么我們來(lái)看一下它是長(zhǎng)啥樣的

圖片轉(zhuǎn)自https://www.chenxun.wiki/2017/01/16/thread-02/

我們看到里面有兩個(gè)東西,Stack(棧)和Heap(堆)。寫(xiě)過(guò)程序的同學(xué)基本都知道Stack是啥東西,我們的每一個(gè)方法(函數(shù))在執(zhí)行的時(shí)候,都會(huì)有一個(gè)棧在維護(hù)它的執(zhí)行順序,比如A方法調(diào)用B方法,那么棧的順序是這樣的:

滿(mǎn)足后入先出的原則,對(duì)于方法調(diào)用來(lái)說(shuō)是非常合適的,比如前面的A調(diào)用B,那肯定B結(jié)束后要恢復(fù)A的執(zhí)行上下文,所以B彈出棧,這個(gè)是很直觀的事情。

程序計(jì)數(shù)器——PC

這里的PC不是指的Personal Computer啊,當(dāng)然不是啦。

程序計(jì)數(shù)器這個(gè)很好理解,它實(shí)際上就是記錄你當(dāng)前的代碼執(zhí)行行號(hào),因?yàn)镴ava作為面向?qū)ο笳Z(yǔ)言,實(shí)際上它的執(zhí)行順序肯定不會(huì)是一路向下的,所以在執(zhí)行過(guò)程中的跳轉(zhuǎn)就需要由PC去做控制。這個(gè)跟下面的JVM Stack和Native Stack是緊密結(jié)合在一起的。

虛擬機(jī)棧——JVM Stack

虛擬機(jī)的信息沒(méi)那么簡(jiǎn)單,但實(shí)際上也差不多。每一個(gè)線(xiàn)程有自己的棧,而棧里面分成一個(gè)個(gè)的棧楨(Stack Frame,大家可以理解為我們上面的A方法和B方法),每個(gè)棧楨就相當(dāng)于是當(dāng)前線(xiàn)程執(zhí)行的上下文,它包含了當(dāng)前函數(shù)執(zhí)行需要的信息,包括:局部變量,參數(shù),返回值等。當(dāng)一個(gè)棧楨(方法)執(zhí)行完成后,它會(huì)從棧中被彈出。這里上面的PC就會(huì)把值更新為當(dāng)前棧頂即將要執(zhí)行的代碼行數(shù),而增加一個(gè)棧楨(調(diào)用另外一個(gè)方法)也是類(lèi)似的,會(huì)構(gòu)建一個(gè)新的棧楨,并把當(dāng)前的棧楨加入到棧頂,再執(zhí)行正常的調(diào)用操作。

這其中,實(shí)際上我們會(huì)留意到,棧楨和棧楨之前多少都會(huì)有一些聯(lián)系,比如上一個(gè)棧楨的返回值是下一個(gè)棧楨的參數(shù)。類(lèi)似這樣的,實(shí)際上棧楨在處理完成的時(shí)候也會(huì)把當(dāng)前的一些返回值作為結(jié)果push到棧中,供下一個(gè)棧使用。

我們?cè)訇P(guān)注一下我們的圖中,我們可以看到JVM Stack是線(xiàn)程隔離的,這就意味著實(shí)際上每個(gè)線(xiàn)程都有自己的棧,在這里面的我們并不需要考慮多線(xiàn)程問(wèn)題。

但沒(méi)有多線(xiàn)程問(wèn)題不代表沒(méi)有任何問(wèn)題,Stack既然是棧,肯定也有棧的固有限制,JVM虛擬機(jī)會(huì)限制棧的大小(可以通過(guò)參數(shù)-Xss進(jìn)行設(shè)置),當(dāng)超過(guò)棧大小時(shí)會(huì)拋出StackOverflowError。

本地方法棧——Native Method Stack

本地方法棧類(lèi)似JVM Stack,區(qū)別最主要的就是JVM是Java內(nèi)部調(diào)用的棧,而Native Method是調(diào)用的本地方法的,這種一般是通過(guò)JNI調(diào)用的,當(dāng)然還有一些其他語(yǔ)言的,比如JPython等基于JVM的腳本語(yǔ)言。

方法區(qū)——Method Area

方法區(qū)是虛擬機(jī)中所有線(xiàn)程共用的一塊區(qū)域,它保存了被當(dāng)前JVM加載的類(lèi)信息、常量、靜態(tài)變量、編譯后產(chǎn)生的代碼等數(shù)據(jù)。當(dāng)我們通過(guò)Class對(duì)象獲取類(lèi)的相關(guān)信息時(shí),都是通過(guò)這里去返回的。這里一般也被劃分作為的一部分,但實(shí)際上它并不屬于,有些資料把它稱(chēng)為Non-Heap以作為區(qū)別。

常量池——Constant Pool

我們?cè)谏厦?strong>方法區(qū)介紹的時(shí)候說(shuō)過(guò)它會(huì)保存當(dāng)前被JVM加載的常量,而我們這里說(shuō)的常量池也是方法區(qū)的一塊。它保存了如字符串常量final變量值類(lèi)名方法名。常量池各細(xì)的可以分為兩種:

?Class常量池

這種實(shí)際上就是我們平常用的最多的,比如類(lèi)名方法名final變量值

?運(yùn)行期常量池

運(yùn)行期常量池從名字來(lái)看就是說(shuō)在程序的運(yùn)行期間是可以修改的。一般情況下,我們?cè)谶\(yùn)行時(shí)的所謂字符串字面量,就是比如String str = "helloworld"類(lèi)似的定義,直接確認(rèn)String值的定義。另外我們看的最多的就是String.intern(這個(gè)也是面試的一個(gè)常見(jiàn)點(diǎn),比如String的對(duì)比啊,以后的文章再展開(kāi)講),它也可以把字符串加入到常量池中。

堆——Heap

進(jìn)入到我們最主要的重頭戲了。是我們JVM最麻煩的區(qū)域,麻煩就麻煩在這里是GC的最主要戰(zhàn)場(chǎng)——垃圾收集的最前線(xiàn)。在JVM中實(shí)例化的對(duì)象及數(shù)組(數(shù)組為什么要特殊拿出來(lái)呢?大家可以思考下)都是存儲(chǔ)在堆上的,這也就意味著它是所有線(xiàn)程共用的,并且是會(huì)有高并發(fā)的問(wèn)題,因?yàn)楣灿靡环?#xff0c;這也就導(dǎo)致了某個(gè)線(xiàn)程對(duì)對(duì)象進(jìn)行修改的時(shí)候,其他線(xiàn)程有可能不能拿到最新的值——高并發(fā)的內(nèi)容我們會(huì)后面涉及。

我們繼續(xù)來(lái)看看的邏輯結(jié)構(gòu)

圖片轉(zhuǎn)自https://juejin.im/post/5dc0e88df265da4d461ebfd0

我們可以看到,堆分為兩個(gè)大塊:

新生代——Young Generation

從新生代的名字就可以看到,這是一些比較年輕的對(duì)象存放的位置,年輕是指對(duì)象從創(chuàng)建到當(dāng)前的時(shí)間都比較短,當(dāng)然,再年輕過(guò)了一段時(shí)間也會(huì)變老,就會(huì)晉升到后面的老年代。這里涉及到了一些GC的算法問(wèn)題,我們后面再學(xué)習(xí)。暫時(shí)先有一個(gè)概念即可。新生代又區(qū)分為幾個(gè):

?Eden區(qū) 新創(chuàng)建的對(duì)象都會(huì)先在該區(qū)(前提是能放得下)?Survivor0區(qū) Eden區(qū)的存活對(duì)象會(huì)遷移到該?Survivor1區(qū) 和Survivor1區(qū)互為備份,兩個(gè)間的對(duì)象會(huì)互相遷移,最后會(huì)以對(duì)象的存活次數(shù)決定是否晉升到老年代。

老年代——Old Generation

從名字也可以看出,老年代當(dāng)然是比較老的對(duì)象呆的地方了。但多老才算老呢,這個(gè)會(huì)由前面的新生代晉升的存活次數(shù)來(lái)決定。

永久代——PermGen Space

其實(shí)嚴(yán)格來(lái)說(shuō),永久代并不屬于里面的,但由于它也叫代,那么為了好看,我們也在這里一起描述。雖然說(shuō)叫永久代,但實(shí)際上它是屬于方法區(qū)的。在JSP時(shí)代,我們經(jīng)常會(huì)遇到一個(gè)錯(cuò)誤叫java.lang.OutOfMemoryError: PermGen space,這一般是由于我們加載的文件太多,導(dǎo)致方法區(qū)超出限制了。而在JDK 8中,HotSpot已經(jīng)把永久代改為元空間(MetaSpace)。我們可以看一下下面這個(gè)小例子,看看怎么用MetaSpace的相關(guān)參數(shù)。

public class MetaSpaceTest { public static void main(String[] args) { StringBuilder sb = new StringBuilder(); IntStream.range(0, Integer.MAX_VALUE).forEach(idx -> { sb.append("str" + idx); }); }}

我們這里設(shè)置最大的MetaSpace為:-XX:MaxMetaspaceSize=128m?這里不斷構(gòu)造新的String,運(yùn)行幾秒后我們會(huì)看到這樣的錯(cuò)誤:

我們可以看到現(xiàn)在已經(jīng)是Heap size的錯(cuò)誤了。這實(shí)際上意味著方法區(qū)已經(jīng)被修改為元空間了。一般情況下它受限于當(dāng)前機(jī)器的物理內(nèi)存。如果說(shuō)它為了解決什么問(wèn)題,最直接還是之前的OOM錯(cuò)誤吧,修改后就可以減少很多OOM的錯(cuò)誤。

JMM是什么

大家一看到JMM,第一眼想到的是啥——這東西該不會(huì)又是啥網(wǎng)絡(luò)用語(yǔ)簡(jiǎn)稱(chēng)吧。難道是姐妹們加美眉加貓貓類(lèi)似的,我只能說(shuō)你想多了。

那么我們就來(lái)開(kāi)謎了,JMM確實(shí)是簡(jiǎn)稱(chēng),但是Java Memory Model——Java內(nèi)存模型的簡(jiǎn)稱(chēng)。

JMM長(zhǎng)啥樣

我們直接來(lái)看看圖

轉(zhuǎn)自https://www.cnblogs.com/kukri/p/9109639.html

這跟我們上面的內(nèi)存結(jié)構(gòu)看著是不是好像有點(diǎn)關(guān)聯(lián)的樣子。我們可以看到每個(gè)線(xiàn)程都有自己的工作內(nèi)存,雖然工作內(nèi)存也是屬于線(xiàn)程的,但它跟我們上面的內(nèi)存結(jié)構(gòu)關(guān)系不大,它們只是主內(nèi)存映射到特定線(xiàn)程的一個(gè)虛擬概念罷了。當(dāng)然,如果為了幫助理解,我們可以這樣理解。工作內(nèi)存實(shí)際上也是屬于JVM Stack的,只是它獨(dú)立于JVM Stack,不歸屬于內(nèi)存結(jié)構(gòu)

我們可以看到圖中,工作內(nèi)存和主內(nèi)存的操作是通過(guò)JMM來(lái)控制的。那具體是怎么控制的呢?

我們先把問(wèn)題放下,先理一下內(nèi)存操作有哪些。讀、寫(xiě),還有呢?沒(méi)了,就這兩個(gè)。那么JMM實(shí)際上要解決的就是讀和寫(xiě)的問(wèn)題。這里又涉及到CPU的MESI協(xié)議,這個(gè)我們大概了解一下先,后面我們講到volatile和高并發(fā)相關(guān)的知識(shí)的時(shí)候我們?cè)偌?xì)說(shuō)。

?Modify——修改

CPU可以發(fā)送標(biāo)志告訴其他CPU說(shuō)這個(gè)變量我已經(jīng)修改了,你們不要再用自己的值了,來(lái)主內(nèi)存中拿。

?Exclusive——獨(dú)占

這種標(biāo)記情況下表明當(dāng)前工作內(nèi)存中的值是獨(dú)占的,其他的CPU并沒(méi)有相應(yīng)的緩存值。

?Shared——共享

表明當(dāng)前的值各CPU上都有

?Invalid——失效

表明當(dāng)前CPU中的值已經(jīng)不能再使用了,需要從主存加載。

為什么需要JMM

我們可以看到,由于每個(gè)線(xiàn)程都會(huì)有自己的工作內(nèi)存——即緩存。對(duì)于共享變量來(lái)說(shuō),某一個(gè)CPU修改了,其他CPU不一定能及時(shí)發(fā)現(xiàn),或者更極端的是一直發(fā)現(xiàn)不了。所以我們需要有一個(gè)協(xié)調(diào)者幫我們?nèi)プ鲞@樣的事情。

共享變量如果被其中一個(gè)CPU改了,那么要通知我,我就不用自己的了,我去主內(nèi)存中重新加載放到工作內(nèi)存中。當(dāng)然JMM的作用不止于此,它還有一些約束讀寫(xiě)禁止重排序等功能。這些我們留待后面的文章再細(xì)說(shuō)。

回到前面的問(wèn)題

說(shuō)了這么多,那么我們前面的問(wèn)題的答案是啥呢?

訪(fǎng)問(wèn)局部變量要不要加鎖呢?

回顧我們前面說(shuō)的堆和棧的情況。如果你說(shuō)局部變量是保存在JVM棧中的,而JVM棧中是線(xiàn)程隔離的,所以可以不加鎖。那么,你肯定是沒(méi)有仔細(xì)看那一塊的相關(guān)知識(shí)。變量包含兩種,一種是基礎(chǔ)變量,一種是引用變量,對(duì)于基礎(chǔ)變量,它直接分配在上,這是線(xiàn)程隔離的,所以如果是基礎(chǔ)變量,隨便來(lái),不需要加鎖;而對(duì)于引用變量,我們知道,只有這個(gè)變量是在上的,而它真正的對(duì)象是在上的,而上的則是需要進(jìn)行加鎖的。我們來(lái)看一個(gè)例子:

public class ConcurrentTest { public static void main(String[] args) { int id = 11; PrimitiveClass primitiveClass = new PrimitiveClass(id); PrimitiveClass primitiveClass2 = new PrimitiveClass(id); primitiveClass.print(); primitiveClass2.print(); ComplexClass complexClass = new ComplexClass(primitiveClass); ComplexClass complexClass2 = new ComplexClass(primitiveClass); complexClass.print(); complexClass2.print(); } static class PrimitiveClass { private long id; public PrimitiveClass(long id) { this.id = id; } public void print() { System.out.println("here is id:" + id); } } static class ComplexClass { private PrimitiveClass primitiveClass; public ComplexClass(PrimitiveClass primitiveClass) { this.primitiveClass = primitiveClass; } public void print() { System.out.println("here is shared:" + primitiveClass); } }}

我們來(lái)看看結(jié)果:我們看到PrimitiveClass實(shí)際上看不出任何區(qū)別,因?yàn)樗腔绢?lèi)型,傳入的值實(shí)際上就是該值本身,并沒(méi)有太多需要特殊處理的地方。而ComplexClass就比較特殊了,它里面有一個(gè)引用類(lèi)型,而且我們看到引用的值實(shí)際上是同一個(gè),所以就意味著,我們對(duì)當(dāng)前的類(lèi)做的改動(dòng),都會(huì)直接體現(xiàn)到該對(duì)象身上。

我們來(lái)看看內(nèi)存示意圖:

雖然我們沒(méi)用多個(gè)線(xiàn)程,但我們new了多次,可以間接理解為我們是用多個(gè)線(xiàn)程的,意會(huì)一下。我們可以看到,對(duì)于對(duì)象,我們都是指向堆中的實(shí)例值,而對(duì)于基本類(lèi)型,它們卻是保存在中的。這也就解釋了我們上面的,局部變量什么時(shí)候需要加鎖,又什么時(shí)候不需要加鎖。

對(duì)象實(shí)例分配在堆中,那是不是都要加鎖呢?

其實(shí)這個(gè)答案我們?cè)谏厦娣治龅臅r(shí)候也順便回答了。如果你的對(duì)象是一個(gè)可變對(duì)象,什么叫可變對(duì)象呢,就是內(nèi)有可變屬性。這個(gè)可變狀態(tài)這里不細(xì)說(shuō)哈,有興趣的同學(xué)可以找找其他文章了解一下。當(dāng)你的對(duì)象擁有可變屬性,那么當(dāng)多線(xiàn)程訪(fǎng)問(wèn)該對(duì)象時(shí),就需要進(jìn)行加鎖。現(xiàn)在流行的函數(shù)式編程在高并發(fā)下有原生的優(yōu)勢(shì)就是因?yàn)樗サ袅?strong>可變屬性,所有結(jié)果都只是一個(gè)中間變量,用完即丟。

那么volatile的實(shí)現(xiàn)原理呢?

回到我們上面的對(duì)JMM的分析上,MESI實(shí)際上就是volatile實(shí)現(xiàn)的基礎(chǔ)。當(dāng)然還有一些更細(xì)的一些東西,比如happens-before原則,禁止重排序等,這些就留到我們后面再來(lái)講。

總結(jié)

今天我們一起了解了JVM內(nèi)存結(jié)構(gòu)和JMM,知道了內(nèi)存結(jié)構(gòu)中分成哪幾部分,并且哪些是線(xiàn)程獨(dú)占,哪些又是共用的;而JMM我們就了解了它出現(xiàn)的原因和基本的MESI協(xié)議。當(dāng)然,很多東西要細(xì)講的話(huà)可以講很多,我們暫時(shí)先這樣,留待后面繼續(xù)。

參考文章:http://tutorials.jenkov.com/java-concurrency/java-memory-model.html

總結(jié)

以上是生活随笔為你收集整理的jvm内存结构_聊聊JVM内存结构的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。