JVM(七)JVM內(nèi)存空間

JVM內(nèi)存空間

JVM規(guī)范在程序運(yùn)行期間定義了不同的數(shù)據(jù)區(qū)域.有一些區(qū)域跟隨JVM的創(chuàng)建銷毀.而有些區(qū)域則是線程獨(dú)有的,線程獨(dú)有的區(qū)域會(huì)跟隨線程的創(chuàng)建與銷毀.

在不同版本和不同廠商的JVM版本中,都會(huì)有較大差異.
本文基于JDK8,HotSpot虛擬機(jī)進(jìn)行的總結(jié)

JVM規(guī)范內(nèi)的運(yùn)行時(shí)數(shù)據(jù)區(qū)域

程序計(jì)數(shù)器(The pc Register)

JVM支持多線程,每個(gè)線程都有自己的程序計(jì)數(shù)器.當(dāng)線程執(zhí)行中的時(shí)候,程序計(jì)數(shù)器會(huì)包含線程的執(zhí)行點(diǎn).(前提是方法不是native方法).

虛擬機(jī)棧(Java Virtual Machine Stacks)

在線程創(chuàng)建的同時(shí),線程會(huì)擁有私有的虛擬機(jī)棧.(線程不共享).虛擬機(jī)棧中會(huì)儲(chǔ)存棧幀(Frames).棧幀中會(huì)持有本地變量,部分結(jié)果,方法的調(diào)用和返回.虛擬機(jī)棧只會(huì)對(duì)棧進(jìn)行壓棧和出棧操作,棧幀可能由堆(heap)分配.虛擬機(jī)棧在內(nèi)存中并不需要是連續(xù)的.JVM規(guī)范規(guī)定具體的虛擬機(jī)實(shí)現(xiàn)必須允許用戶對(duì)虛擬機(jī)棧的大小進(jìn)行調(diào)整

堆(Heap)

堆內(nèi)存在JVM內(nèi)線程共享的.堆內(nèi)存為所有的類示例和數(shù)組進(jìn)行內(nèi)存分配.

堆內(nèi)存創(chuàng)建于JVM的啟動(dòng)階段.堆中的對(duì)象存儲(chǔ)由垃圾收集器進(jìn)行管理,對(duì)象從不會(huì)明確已經(jīng)被回收.JVM不承擔(dān)對(duì)象的管理,具體實(shí)現(xiàn)由各廠商根據(jù)系統(tǒng)需求實(shí)現(xiàn).堆內(nèi)存的大小是可用動(dòng)態(tài)擴(kuò)展的,并且堆內(nèi)存不必是連續(xù)的.

方法區(qū)(Method Area)

方法區(qū)也是JVM中的線程共享的.存儲(chǔ)的結(jié)構(gòu),例如運(yùn)行時(shí)常量池,字段和方法數(shù)據(jù),和結(jié)構(gòu)方法.

方法區(qū)創(chuàng)建于JVM的啟動(dòng)階段.

方法區(qū)在邏輯上是堆內(nèi)存的一部分.

方法區(qū)的簡(jiǎn)單實(shí)現(xiàn)可以選擇不對(duì)其進(jìn)行垃圾回收和整理.

HotSpot的實(shí)現(xiàn)會(huì)對(duì)此進(jìn)行垃圾收集

方法區(qū)的大小應(yīng)該設(shè)計(jì)為可調(diào)整,在內(nèi)存中可以不連續(xù).

運(yùn)行時(shí)常量池(Run-Time Constant Pool)

運(yùn)行時(shí)常量池是每個(gè)類與接口在運(yùn)行時(shí)才能確定的常量.

例如 Sting const = UUID.randomUUID().toString();

每一個(gè)運(yùn)行時(shí)常量池都是從方法區(qū)中分配的.

本地方法棧(Native Method Stacks)

本地方法棧在線程創(chuàng)建的時(shí)候進(jìn)行分配,是線程獨(dú)有的.

JVM規(guī)范規(guī)定本地方法??梢允枪潭ù笮?也可以是可調(diào)整的.

jvm規(guī)范定義的內(nèi)存區(qū)域

HotSpot虛擬機(jī)的運(yùn)行時(shí)內(nèi)存結(jié)構(gòu)

jmc查看內(nèi)存劃分

jmc查看內(nèi)存劃分

在上圖中,可以看到內(nèi)存區(qū)域主要分為兩大類,heap,Non-heap

虛擬機(jī)棧(Stack)

線程獨(dú)有的內(nèi)存空間,每個(gè)方法在執(zhí)行時(shí)候會(huì)形成一個(gè)棧幀,用于存在這個(gè)方法的局部變量,操作數(shù)棧,動(dòng)態(tài)鏈接,方法返回等信息.

程序計(jì)數(shù)器(The pc Register)

線程獨(dú)有,描述的是JVM執(zhí)行過(guò)程中程序的執(zhí)行順序,保證在CPU切換過(guò)程中對(duì)執(zhí)行方法順序的記錄.

本地方法棧(Native Method Stacks)

native方法的區(qū)域.與虛擬機(jī)棧類似
HotSpot的JVM中,將本地方法棧虛擬機(jī)棧合二為一了

堆(Heap)

一般來(lái)說(shuō),new出來(lái)的對(duì)象放在,但是由于Java中不能直接使用對(duì)象的,而是通過(guò)引用,而對(duì)象的引用放在虛擬機(jī)棧,是局部變量表中的一個(gè)局部變量.

在堆中創(chuàng)建的對(duì)象,會(huì)有持有指向方法區(qū)的指針,因?yàn)閷?duì)象的信息存儲(chǔ)在方法區(qū)中.

由于GC都采用分代收集算法,所以堆內(nèi)存的對(duì)象也進(jìn)行了相應(yīng)的劃分.

堆內(nèi)存在物理上可以是連續(xù),也可以是不連續(xù)的.

方法區(qū)(MethodArea)

在HotSpot的實(shí)現(xiàn)中,將元空間(MetaSpace)放在方法區(qū)中.
線程共享的區(qū)域,存在類的信息,常量,靜態(tài)變量等.進(jìn)行GC的可能性較低.

從JDK8后就已經(jīng)沒(méi)有了永久代,使用metaSpace(元空間)

官方說(shuō)法


元空間
  • JDK8不再有永久代
  • 類的元信息被存儲(chǔ)在一個(gè)信息空間,稱為MetaSpace
  • 內(nèi)存是不連續(xù)的
  • 元空間是從本地內(nèi)存中分配的
  • MetaSpace最大可用空間就是系統(tǒng)可用內(nèi)存
  • 可以被MaxMetaspaceSize參數(shù)進(jìn)行限制

永久代移除譯文

直接內(nèi)存.(Direct memory)

與NIO相關(guān).
JVM通過(guò)堆上的DirectByteBuffer操作直接內(nèi)存

字節(jié)碼緩存(code cache)

Non-heap區(qū)域,用于存儲(chǔ)由JIT編譯期生成的編譯后代碼.

  • 由內(nèi)存直接分配
  • 由Code Cache 清理器管理

總結(jié)圖

各個(gè)區(qū)域之間的聯(lián)系

關(guān)于堆內(nèi)存是線程共享的說(shuō)法,其實(shí)并不嚴(yán)謹(jǐn),因?yàn)檫€存在LTAB,在后續(xù)會(huì)繼續(xù)學(xué)習(xí)記錄.

創(chuàng)建對(duì)象的過(guò)程

使用new關(guān)鍵字創(chuàng)對(duì)對(duì)象實(shí)例.
JVM會(huì)判斷這個(gè)對(duì)象對(duì)應(yīng)的是否已經(jīng)被加載.如果沒(méi)有則進(jìn)行類加載過(guò)程.

1.在堆內(nèi)存中創(chuàng)建對(duì)象的實(shí)例

  • 1.指針碰撞(內(nèi)存中被占用與未占用內(nèi)存空間分隔開(kāi))
  • 2.空閑列表(內(nèi)存中被占用和未被占用的空間交織在一起)

兩種方法與GC的機(jī)制有關(guān),要看具體GC發(fā)生后是否會(huì)對(duì)對(duì)象進(jìn)行壓縮和移動(dòng).

2.為對(duì)象的成員變量賦初始值

3.將對(duì)象的引用返回

對(duì)象在內(nèi)存中的布局

1.對(duì)象頭
2.實(shí)例數(shù)據(jù)
3.對(duì)齊填充(可選)

棧中對(duì)象使用,是通過(guò)引用的方式.而引用又有兩種方式

  • 1.句柄.堆數(shù)據(jù)一部分是對(duì)象在堆中的指針,另一部分是對(duì)象的類信息指針,類信息指針指向方法區(qū)
  • 2.指針.堆中一部分?jǐn)?shù)據(jù)存放實(shí)例對(duì)象,另一部分存放類信息的指針
對(duì)象內(nèi)存分布

JVM運(yùn)行時(shí)數(shù)據(jù)區(qū)域

JVM對(duì)象內(nèi)存區(qū)域例子

這個(gè)例子中,生成了2部分的內(nèi)存區(qū)域

  • 1.obj這個(gè)引用變量,因?yàn)槭欠椒▋?nèi)的變量,所以放到stack里面
  • 2.真正Object class的實(shí)例對(duì)象,放在Heap里面

這個(gè)例子中,一個(gè)消耗12bytes,JVM規(guī)定引用占4個(gè)bytes,空對(duì)象占8bytes

當(dāng)方法執(zhí)行結(jié)束后,對(duì)應(yīng)stack中的變量馬上回出棧,而Heap中的對(duì)象需要等待GC進(jìn)行回收

Compressed Class Space&UseCompressedOops

MetaSpace默認(rèn)會(huì)將元數(shù)據(jù)和類信息放在同一個(gè)區(qū)域,當(dāng)
UseCompressedClassesPointers啟用后,會(huì)將類信息和元數(shù)據(jù)分為兩部分存儲(chǔ)
,MaxMetaspaceSize將會(huì)設(shè)置兩部分空間的上限

啟用前的存儲(chǔ)形式


未啟用前

啟用后的存儲(chǔ)形式


啟用后

虛擬機(jī)參數(shù)-XX+UseCompressedOops使用的效果是,當(dāng)從32bit的虛擬機(jī)遷移到64bit的虛擬機(jī)上,JVM會(huì)將部分的指針進(jìn)行壓縮,防止在64bit系統(tǒng)中占用更大的內(nèi)存

TLAB

TLAB(Thread Local Allocation Buffer).是用于多線程分配對(duì)象的一個(gè)堆內(nèi)存區(qū)域,能夠提升多線程下并發(fā)創(chuàng)建對(duì)象造成競(jìng)爭(zhēng)的性能下降.
TLAB位于年輕代

TLAB

深入請(qǐng)參考

空間分配擔(dān)保

當(dāng)發(fā)生MinorGC時(shí),虛擬機(jī)會(huì)檢查老年代最大可用的連續(xù)空間是否大于新生代所有對(duì)象的總空間,如果這個(gè)條件成立,那么MinorGC可以確保是安全的.

當(dāng)大量對(duì)象在MinorGC后仍然存活,就需要老年代進(jìn)行分空間分配擔(dān)保,把Survivor無(wú)法容納的對(duì)象直接進(jìn)入老年代.

如果老年代判斷剩余空間不足(根據(jù)以往每一次回收晉升到老年代對(duì)象容量的平均值作為判定值),進(jìn)行FullGC,

參考資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 工作之余,想總結(jié)一下JVM相關(guān)知識(shí)。 Java運(yùn)行時(shí)數(shù)據(jù)區(qū): Java虛擬機(jī)在執(zhí)行Java程序的過(guò)程中會(huì)將其管理的...
    Huang遠(yuǎn)閱讀 687評(píng)論 0 2
  • 內(nèi)存溢出和內(nèi)存泄漏的區(qū)別 內(nèi)存溢出:out of memory,是指程序在申請(qǐng)內(nèi)存時(shí),沒(méi)有足夠的內(nèi)存空間供其使用,...
    Aimerwhy閱讀 808評(píng)論 0 1
  • JVM內(nèi)存管理 與其他高級(jí)語(yǔ)言(例如C和C++)不同,在Java中我們基本上不會(huì)顯式地調(diào)用分配內(nèi)存的函數(shù),我們甚至...
    生瓜蛋子閱讀 189評(píng)論 0 1
  • Java 虛擬機(jī)(Java virtual machine,JVM)是運(yùn)行 Java 程序必不可少的機(jī)制。JVM實(shí)...
    Rick617閱讀 976評(píng)論 0 0
  • 彩云今年二十出頭,性格爽直,成天瞇著眼笑,說(shuō)話大大咧咧,有點(diǎn)沒(méi)心沒(méi)肺的樣子,這樣假小子的性格,為她贏得了不少好人緣...
    玥淼閱讀 522評(píng)論 0 1

友情鏈接更多精彩內(nèi)容