1.年輕代存活的對象太多,老年代了放不下
01.示例代碼
public class DemoTest1 {
02.啟動JVM參數(shù)
-XX:NewSize=10485760 -XX:MaxNewSize=10485760 -XX:InitialHeapSize=20971520 -XX:MaxHeapSize=20971520 -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15 -XX:PretenureSizeThreshold=3145728 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:gc.log
其中,參數(shù)-XX:PretenureSizeThreshold
,參數(shù)要設(shè)置大對象閾值為3MB晃虫,也就是超過3MB徊都,就直接進入老年代噩斟。
大對象大小是3MB。一旦對象大小超過3MB意乓,不會進入新生代烫罩,直接進入老年代惜傲。
啟動命令:
java -jar -XX:NewSize=10485760 -XX:MaxNewSize=10485760 -XX:InitialHeapSize=20971520 -XX:MaxHeapSize=20971520 -XX:SurvivorRatio=8 -XX:MaxTenuringThre
03.GC日志
啟動之后就得到如下GC日志:
Java HotSpot(TM) 64-Bit Server VM (25.151-b12) for windows-amd64 JRE (1.8.0_151-b12), built on Sep 5 2017 19:33:46 by "java_re" with MS VC++ 10.0 (VS2010)
04.分析GC日志
先看如下代碼:
byte[] array1 = new byte[4 * 1024 * 1024];
這行代碼直接分配了一個4MB的大對象,此時這個對象會直接進入老年代贝攒,接著array1不再引用這個對象。
此時內(nèi)存分配如下:
緊接著就是如下代碼
byte[] array2 = new byte[2 * 1024 * 1024];
連續(xù)分配了4個數(shù)組时甚,其中3個是2MB的數(shù)組隘弊,1個是128KB的數(shù)組,如下圖所示荒适,全部會進入Eden區(qū)域中梨熙。
接著會執(zhí)行如下代碼:byte[] array6 = new byte[2 * 1024 * 1024];
。此時還能放得下2MB的對象嗎刀诬?
不可能了咽扇,因為Eden區(qū)已經(jīng)放不下了。因此此時會直接觸發(fā)一次Young GC。
我們看下面的GC日志:
0.174: [GC (Allocation Failure) 0.174: [ParNew (promotion failed): 7457K->8328K(9216K), 0.0046949 secs]
這行日志顯示了质欲,Eden區(qū)原來是有7000多KB的對象树埠,但是回收之后發(fā)現(xiàn)一個都回收不掉,因為上述幾個數(shù)組都被變量引用了嘶伟。
所以此時怎憋,一定會直接把這些對象放入到老年代里去,但是此時老年代里已經(jīng)有一個4MB的數(shù)組了九昧,還能放的下3個2MB的數(shù)組和1個128KB的數(shù)組嗎绊袋?
明顯是不行的,此時一定會超過老年代的10MB大小铸鹰。
所以此時看gc日志:
0.179: [CMS: 8194K->6962K(10240K), 0.0033396 secs] 11553K->6962K(19456K), [Metaspace: 2970K->2970K(1056768K)], 0.0089224 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
此時執(zhí)行了CMS垃圾回收器的Full GC癌别,F(xiàn)ull GC其實就是會對老年代進行Old GC,同時一般會跟一次Young GC關(guān)聯(lián)蹋笼,還會觸發(fā)一次元數(shù)據(jù)區(qū)(永久代)的GC规个。
在CMS Full GC之前,就已經(jīng)觸發(fā)過Young GC了姓建,此時可以看到此時Young GC就已經(jīng)有了诞仓,接著就是執(zhí)行針對老年代的Old GC,也就是如下日志:
CMS: 8194K->6962K(10240K), 0.0033396 secs
這里看到老年代從8MB左右的對象占用速兔,變成了6MB左右的對象占用墅拭,這是怎么個過程呢?
很簡單涣狗,一定是在Young GC之后谍婉,先把2個2MB的數(shù)組放入了老年代,如下圖镀钓。
此時要繼續(xù)放1個2MB的數(shù)組和1個128KB的數(shù)組到老年代穗熬,一定會放不下,所以此時就會觸發(fā)CMS的Full GC丁溅。
然后此時就會回收掉其中的一個4MB的數(shù)組唤蔗,因為他已經(jīng)沒人引用了,如下圖所示窟赏。
所以再看CMS的垃圾回收日志:CMS: 8194K->6962K(10240K), 0.0033396 secs
妓柜,他是從回收前的8MB變成了6MB,就是上圖所示涯穷。
最后在CMS Full GC執(zhí)行完畢之后棍掐,其實年輕代的對象都進入了老年代,此時最后一行代碼要在年輕代分配2MB的數(shù)組就可以成功了拷况,如下圖作煌。
05.總結(jié)
這是一個觸發(fā)老年代GC的案例掘殴,就是年輕代存活的對象太多放不下老年代了,此時就會觸發(fā)CMS的Full GC粟誓。
2.老年代可用空間小于了歷次Young GC后升入老年代的對象的平均大小
01.示例代碼
public class DemoTest1 {
02.啟動JVM參數(shù)
-XX:NewSize=5M -XX:MaxNewSize=5M -XX:InitialHeapSize=10M -XX:MaxHeapSize=10M -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15 -XX:PretenureSizeThreshold=2M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:gc.log
其中奏寨,參數(shù)-XX:PretenureSizeThreshold
,參數(shù)要設(shè)置大對象閾值為2MB,也就是超過2MB努酸,就直接進入老年代服爷。
大對象大小是3MB。一旦對象大小超過3MB获诈,不會進入新生代仍源,直接進入老年代。
啟動命令:
java -jar -XX:NewSize=5M -XX:MaxNewSize=5M -XX:InitialHeapSize=10M -XX:MaxHeapSize=10M -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15 -XX:PretenureSizeThreshold=2M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:gc.log jvm-demo.jar
03.GC日志
啟動之后就得到如下GC日志:
老年代
年輕代
Java HotSpot(TM) 64-Bit Server VM (25.151-b12) for windows-amd64 JRE (1.8.0_151-b12), built on Sep 5 2017 19:33:46 by "java_re" with MS VC++ 10.0 (VS2010)
04.分析GC日志
(1).代碼塊1
先看如下代碼:
byte[] array1 = new byte[1 * 1024 * 1024];
這段代碼直接分配了4個1MB的數(shù)組舔涎,并且在第4個數(shù)組的時候笼踩,會因為新生代內(nèi)存不足觸發(fā)YGC。
此時內(nèi)存分配如下:
對應(yīng)如下GC日志:
0.121: [GC (Allocation Failure) 0.121: [ParNew: 3155K->512K(4608K), 0.0041165 secs] 3155K->766K(9728K), 0.0042644 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
此時亡嫌,可以看到新生代就只剩512K的對象嚎于,這個奇怪的512KB的對象進入Survivor From區(qū)。
那么大小為1MB的數(shù)組對象去哪里呢挟冠?肯定不是這個奇怪的512KB的對象于购。
這1MB的數(shù)組首先肯定是準備進入Survivor From區(qū),可是知染,在我們設(shè)置的JVM參數(shù)下肋僧,只有0.5MB,明顯是不夠分配的控淡。根據(jù)JVM YoungGC的規(guī)則,Survivor區(qū)放不下GC之后存活的對象嫌吠,直接進入老年代。
所以掺炭,1MB的數(shù)組對象是直接進入到老年代了辫诅。
此時,內(nèi)存分配如下:
(2).代碼塊2
緊接這就是這塊代碼:
array1 = new byte[1 * 1024 * 1024];
這里再次創(chuàng)建了3個1MB的數(shù)組對象涧狮,并且會觸發(fā)一次YoungGC炕矮;
對應(yīng) GC日志如下:
0.125: [GC (Allocation Failure) 0.125: [ParNew: 3663K->0K(4608K), 0.0016667 secs] 3917K->1732K(9728K), 0.0017448 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
此時,Young GC之后勋篓,新生代變成0KB吧享,那么存活的大小為1MB的數(shù)組對象去哪里呢?
這1MB的數(shù)組首先肯定是準備進入Survivor From區(qū)譬嚣,可是,在我們設(shè)置的JVM參數(shù)下钞它,只有0.5MB拜银,明顯是不夠分配的殊鞭。根據(jù)JVM YoungGC的規(guī)則,Survivor區(qū)放不下GC之后存活的對象,直接進入老年代尼桶。
所以操灿,1MB的數(shù)組對象是直接進入到老年代了。
之前看到的未知的對象512KB也進入到老年代泵督,此時內(nèi)存分配如下:
(3).代碼塊3
array3 = null;
這里再次創(chuàng)建了3個1MB的數(shù)組對象趾盐,并且會觸發(fā)一次YoungGC;
對應(yīng)的GC日志如下:
0.127: [GC (Allocation Failure) 0.127: [ParNew: 3142K->0K(4608K), 0.0013221 secs] 4875K->2756K(9728K), 0.0013592 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
此時內(nèi)存分配如下:
(4).代碼塊4
array2 = null;
這里再次創(chuàng)建了3個1MB的數(shù)組對象小腊,并且會觸發(fā)一次YoungGC救鲤;并且在這兒,觸發(fā)Young GC之前觸發(fā)了一次CMS的Old GC,觸發(fā)的條件就是老年代可用空間小于了歷次Young GC后升入老年代的對象的平均大小秩冈。此時新生代大小變成0KB
對應(yīng)的GC日志如下:
0.129: [GC (CMS Initial Mark) [1 CMS-initial-mark: 2756K(5120K)] 4878K(9728K), 0.0004498 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
此時內(nèi)存分配如下:
(5).代碼塊5
array1 = null;
此時本缠,再創(chuàng)建3個1MB的數(shù)組對象,再次觸發(fā)一次Young GC入问,執(zhí)行完YoungGC丹锹,此時新生代大小變成0KB;
對應(yīng)的GC日志如下:
0.131: [GC (Allocation Failure) 0.131: [ParNew: 3148K->0K(4608K), 0.0007974 secs] 5904K->3780K(9728K), 0.0008262 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
此時內(nèi)存分配如下:
(6).總結(jié)
如下GC堆內(nèi)存日志我們也可以去驗證下上面的推測:
此時新生代使用了53%的大小芬失,我們還有一個1MB的數(shù)組楣黍,可能還存在一些未知對象。
在老年代中使用了大約3MB的空間棱烂,應(yīng)該就是上圖中的對象租漂。
Heap
3.幾個觸發(fā)Full GC的條件
第一:是老年代可用內(nèi)存小于新生代全部對象的大小,如果沒開啟空間擔(dān)保參數(shù)垢啼,會直接觸發(fā)Full GC窜锯,所以一般空間擔(dān)保參數(shù)都會打開;注:jDK1.8之后已經(jīng)取消了
-XX:-HandlePromotionFailure
機制第二:是老年代可用內(nèi)存小于歷次新生代GC后進入老年代的平均對象大小芭析,此時會提前Full GC锚扎;
第三:是新生代Minor GC后的存活對象大于Survivor,那么就會進入老年代馁启,此時老年代內(nèi)存不足驾孔。
上述情況都會導(dǎo)致老年代Full GC。
第四:就是“-XX:CMSInitiatingOccupancyFaction”參數(shù)惯疙,
如果老年代可用內(nèi)存大于歷次新生代GC后進入老年代的對象平均大小翠勉,但是老年代已經(jīng)使用的內(nèi)存空間超過了這個參數(shù)指定的比例,也會自動觸發(fā)Full GC霉颠。默認92%
最后謝謝大家閱讀对碌,JVM之復(fù)雜,是要用頭發(fā)絲作為代價去丈量的蒿偎。向JVM沖刺朽们,離JVM近點怀读,我的頭發(fā)絲就少點。文中若有錯誤骑脱,歡迎各位指正菜枷,謝謝大家。