在現(xiàn)實(shí)社會(huì)中绰播,借款會(huì)指定擔(dān)保人滔韵,就是當(dāng)借款人還不起錢宴卖,就由擔(dān)保人來還錢肝谭。
在JVM的內(nèi)存分配時(shí)医寿,也有這樣的內(nèi)存分配擔(dān)保機(jī)制沟突。就是當(dāng)在新生代無法分配內(nèi)存的時(shí)候,把新生代的對(duì)象轉(zhuǎn)移到老生代,然后把新對(duì)象放入騰空的新生代秀鞭。
現(xiàn)在假設(shè)剩辟,我們的新生代分為三個(gè)區(qū)域,分別為eden space,from space和to space刺桃。
現(xiàn)在是嘗試分配三個(gè)2MB的對(duì)象和一個(gè)4MB的對(duì)象,然后我們通過JVM參數(shù) -Xms20M、-Xmx20M逼纸、-Xmn10M 把Java堆大小設(shè)置為20MB,不可擴(kuò)展哥力。
其中10M分配給新生代吩跋,另外10M分配給老生代。
然后我們通過-XX:SurvivorRatio=8來分配新生代各區(qū)的比例旺韭,設(shè)置為8,表示eden與一個(gè)survivor區(qū)的空間比例為8:1悔政。
圖1 新生代內(nèi)存分配
JVM參數(shù)配置:
-Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:+UseSerialGC
這里我們先手動(dòng)指定垃圾收集器為客戶端模式下的Serial+Serial Old的收集器組合進(jìn)行內(nèi)存回收近弟。
由于不同的收集器的收集機(jī)制不同鳖敷,為了呈現(xiàn)出內(nèi)存分配的擔(dān)保效果定踱,我們這里需要手動(dòng)指定為Serial+Serial Old模式。
另外擔(dān)保機(jī)制在JDK1.5以及之前版本中默認(rèn)是關(guān)閉的哺壶,需要通過HandlePromotionFailure手動(dòng)指定屋吨,JDK1.6之后就默認(rèn)開啟。這里我們使用的是JDK1.8山宾,所以不用再手動(dòng)去開啟擔(dān)保機(jī)制至扰。
下面我們新建四個(gè)byte數(shù)組,前三個(gè)分別為2MB大小的內(nèi)存分配资锰,第四個(gè)是4MB的內(nèi)存分配敢课。代碼如下:
然后運(yùn)行程序,看看GC日志:
[GC (Allocation Failure) [DefNew: 7836K->472K(9216K), 0.0120087 secs] 7836K->6616K(19456K), 0.0123203 secs] [Times: user=0.01 sys=0.01, real=0.01 secs]
Heap
def new generation total 9216K, used 4732K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
eden space 8192K, 52% used [0x00000007bec00000, 0x00000007bf0290f0, 0x00000007bf400000)
from space 1024K, 46% used [0x00000007bf500000, 0x00000007bf576018, 0x00000007bf600000)
to space 1024K, 0% used [0x00000007bf400000, 0x00000007bf400000, 0x00000007bf500000)
tenured generation total 10240K, used 6144K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
the space 10240K, 60% used [0x00000007bf600000, 0x00000007bfc00030, 0x00000007bfc00200, 0x00000007c0000000)
Metaspace used 3160K, capacity 4494K, committed 4864K, reserved 1056768K
class space used 341K, capacity 386K, committed 512K, reserved 1048576K
通過GC日志我們發(fā)現(xiàn)在分配allocation4的時(shí)候绷杜,發(fā)生了一次Minor GC直秆,讓新生代從7836K變?yōu)榱?72K,但是你發(fā)現(xiàn)整個(gè)堆的占用并沒有多少變化鞭盟。這是因?yàn)榍懊嫒齻€(gè)2MB的對(duì)象都還存活著圾结,所以回收器并沒有找到可回收的對(duì)象。但為什么會(huì)出現(xiàn)這次GC呢齿诉?
圖2 正常流程把前三個(gè)對(duì)象放入了新生代Eden區(qū)
如果你算一筆賬就知道了筝野,前面三個(gè)對(duì)象2MB+2MB+2MB=6MB。
虛擬機(jī)分配內(nèi)存優(yōu)先會(huì)分配到新生代的eden space粤剧,通過圖1我們知道新生代可用內(nèi)存一共只有9216KB歇竟,現(xiàn)在新生代已經(jīng)被用去了6MB,還剩下9216KB-6144KB=3072KB抵恋,然而第四個(gè)對(duì)象是4MB焕议,顯然在新生代已經(jīng)裝不下了。
圖3 第四個(gè)對(duì)象此時(shí)無法放入Eden區(qū)
于是發(fā)生了一次Minor GC弧关!
而且本次GC期間盅安,虛擬機(jī)發(fā)現(xiàn)eden space的三個(gè)對(duì)象(6MB)又無法全部放入Survivor空間(Survivor可用內(nèi)存只有1MB)唤锉。
這時(shí)候該怎么辦呢?第四個(gè)對(duì)象還要不要分配呢宽堆?
此時(shí)腌紧,JVM就啟動(dòng)了內(nèi)存分配的擔(dān)保機(jī)制,把這6MB的三個(gè)對(duì)象直接轉(zhuǎn)移到了老年代畜隶。
此時(shí)就把新生代的空間騰出來了,然后把第四個(gè)對(duì)象(4MB)放入了Eden區(qū)中号胚,所以你看到的結(jié)果是4096/8192=0.5籽慢,也就是約50%:
eden space 8192K, 52% used [0x00000007bec00000, 0x00000007bf0290f0, 0x00000007bf400000)
老年代則被占用了6MB,也就是前三個(gè)對(duì)象猫胁,102423=6144KB箱亿,6144KB/10240KB=0.6也就是60%:
the space 10240K, 60% used [0x00000007bf600000, 0x00000007bfc00030, 0x00000007bfc00200, 0x00000007c0000000)
圖4:擔(dān)保后,allocation4放入到新生代eden區(qū)
圖5:擔(dān)保后弃秆,之前在新生代的三個(gè)對(duì)象轉(zhuǎn)移到了老生代
服務(wù)端模式下的擔(dān)保機(jī)制實(shí)現(xiàn)
上面我們演示的在客戶端模式(Serial+Serial Old)的場(chǎng)景下的結(jié)果届惋,接下來我們使用服務(wù)端模式(Parallel Scavenge+Serial Old的組合)來看看擔(dān)保機(jī)制的實(shí)現(xiàn)。
修改GC組合為:-XX:+UseParallelGC
然后我們運(yùn)行程序看看GC日志菠赚。
- 第四個(gè)對(duì)象是4MB的情況下:
[GC (Allocation Failure) [PSYoungGen: 6156K->592K(9216K)] 6156K->4696K(19456K), 0.0032059 secs] [Times: user=0.01 sys=0.01, real=0.01 secs]
Heap
PSYoungGen total 9216K, used 7057K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
eden space 8192K, 78% used [0x00000007bf600000,0x00000007bfc505f8,0x00000007bfe00000)
from space 1024K, 57% used [0x00000007bfe00000,0x00000007bfe94010,0x00000007bff00000)
to space 1024K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007c0000000)
ParOldGen total 10240K, used 4104K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
object space 10240K, 40% used [0x00000007bec00000,0x00000007bf002020,0x00000007bf600000)
Metaspace used 3299K, capacity 4494K, committed 4864K, reserved 1056768K
class space used 357K, capacity 386K, committed 512K, reserved 1048576K
- 第四個(gè)對(duì)象是3MB的情況下:
[GC (Allocation Failure) [PSYoungGen: 8192K->544K(9216K)] 8192K->6688K(19456K), 0.0052943 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
[Full GC (Ergonomics) [PSYoungGen: 544K->0K(9216K)] [ParOldGen: 6144K->6627K(10240K)] 6688K->6627K(19456K), [Metaspace: 3286K->3286K(1056768K)], 0.0063048 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
Heap
PSYoungGen total 9216K, used 3238K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
eden space 8192K, 39% used [0x00000007bf600000,0x00000007bf929918,0x00000007bfe00000)
from space 1024K, 0% used [0x00000007bfe00000,0x00000007bfe00000,0x00000007bff00000)
to space 1024K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007c0000000)
ParOldGen total 10240K, used 6627K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
object space 10240K, 64% used [0x00000007bec00000,0x00000007bf278f70,0x00000007bf600000)
Metaspace used 3294K, capacity 4494K, committed 4864K, reserved 1056768K
class space used 356K, capacity 386K, committed 512K, reserved 1048576K
發(fā)現(xiàn)當(dāng)我們使用Server模式下的ParallelGC收集器組合(Parallel Scavenge+Serial Old的組合)下脑豹,擔(dān)保機(jī)制的實(shí)現(xiàn)和之前的Client模式下(SerialGC收集器組合)有所變化。在GC前還會(huì)進(jìn)行一次判斷衡查,如果要分配的內(nèi)存>=Eden區(qū)大小的一半瘩欺,那么會(huì)直接把要分配的內(nèi)存放入老年代中。否則才會(huì)進(jìn)入擔(dān)保機(jī)制拌牲。
這里我們的第四個(gè)對(duì)象是4MB的時(shí)候俱饿,也就是(1024KB*4)/8192KB=0.5,剛好一半塌忽,于是就這第四個(gè)對(duì)象分配到了老年代拍埠。
第二次,我們把第四個(gè)對(duì)象由4MB土居,改為3MB枣购,此時(shí)3MB/8192KB=0.37,顯然不到一半装盯,此時(shí)發(fā)現(xiàn)3MB還是無法放入坷虑,那么就執(zhí)行擔(dān)保機(jī)制,把前三個(gè)對(duì)象轉(zhuǎn)移到老生代埂奈,然后把第四個(gè)對(duì)象(3MB)放入eden區(qū)迄损。
總結(jié)
內(nèi)存分配是在JVM在內(nèi)存分配的時(shí)候,新生代內(nèi)存不足時(shí)账磺,把新生代的存活的對(duì)象搬到老生代芹敌,然后新生代騰出來的空間用于為分配給最新的對(duì)象痊远。這里老生代是擔(dān)保人。在不同的GC機(jī)制下氏捞,也就是不同垃圾回收器組合下碧聪,擔(dān)保機(jī)制也略有不同。在Serial+Serial Old的情況下液茎,發(fā)現(xiàn)放不下就直接啟動(dòng)擔(dān)保機(jī)制逞姿;在Parallel Scavenge+Serial Old的情況下,卻是先要去判斷一下要分配的內(nèi)存是不是>=Eden區(qū)大小的一半捆等,如果是那么直接把該對(duì)象放入老生代滞造,否則才會(huì)啟動(dòng)擔(dān)保機(jī)制。
本文分享自微信公眾號(hào) - ImportSource(importsource)栋烤,作者:賀卓凡
原文出處及轉(zhuǎn)載信息見文內(nèi)詳細(xì)說明谒养,如有侵權(quán),請(qǐng)聯(lián)系 yunjia_community@tencent.com 刪除明郭。
原始發(fā)表時(shí)間:2017-12-10