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)整的.

HotSpot虛擬機(jī)的運(yùn)行時(shí)內(nèi)存結(jié)構(gòu)
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é)圖

關(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ì)象,另一部分存放類信息的指針

JVM運(yùn)行時(shí)數(shù)據(jù)區(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位于年輕代

深入請(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,