图解JVM实验-触发FullGC的几个条件

1.年轻代存活的对象太多,老年代了放不下

01.示例代码

public class DemoTest1 {

02.启动JVM参数

-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

其中,参数-XX:PretenureSizeThreshold,参数要设置大对象阈值为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不再引用这个对象。

此时内存分配如下:

image

紧接着就是如下代码

byte[] array2 = new byte[2 * 1024 * 1024];

连续分配了4个数组,其中3个是2MB的数组,1个是128KB的数组,如下图所示,全部会进入Eden区域中。

image

接着会执行如下代码:byte[] array6 = new byte[2 * 1024 * 1024];。此时还能放得下2MB的对象吗?

不可能了,因为Eden区已经放不下了。因此此时会直接触发一次Young GC。

我们看下面的GC日志:

0.174: [GC (Allocation Failure) 0.174: [ParNew (promotion failed): 7457K->8328K(9216K), 0.0046949 secs]

这行日志显示了,Eden区原来是有7000多KB的对象,但是回收之后发现一个都回收不掉,因为上述几个数组都被变量引用了。

所以此时,一定会直接把这些对象放入到老年代里去,但是此时老年代里已经有一个4MB的数组了,还能放的下3个2MB的数组和1个128KB的数组吗?

明显是不行的,此时一定会超过老年代的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]

此时执行了CMS垃圾回收器的Full GC,Full GC其实就是会对老年代进行Old GC,同时一般会跟一次Young GC关联,还会触发一次元数据区(永久代)的GC。

在CMS Full GC之前,就已经触发过Young GC了,此时可以看到此时Young GC就已经有了,接着就是执行针对老年代的Old GC,也就是如下日志:

CMS: 8194K->6962K(10240K), 0.0033396 secs

这里看到老年代从8MB左右的对象占用,变成了6MB左右的对象占用,这是怎么个过程呢?

很简单,一定是在Young GC之后,先把2个2MB的数组放入了老年代,如下图。

image

此时要继续放1个2MB的数组和1个128KB的数组到老年代,一定会放不下,所以此时就会触发CMS的Full GC。

然后此时就会回收掉其中的一个4MB的数组,因为他已经没人引用了,如下图所示。

image

所以再看CMS的垃圾回收日志:CMS: 8194K->6962K(10240K), 0.0033396 secs,他是从回收前的8MB变成了6MB,就是上图所示。

最后在CMS Full GC执行完毕之后,其实年轻代的对象都进入了老年代,此时最后一行代码要在年轻代分配2MB的数组就可以成功了,如下图。

image

05.总结

这是一个触发老年代GC的案例,就是年轻代存活的对象太多放不下老年代了,此时就会触发CMS的Full GC。

2.老年代可用空间小于了历次Young GC后升入老年代的对象的平均大小

01.示例代码

public class DemoTest1 {

02.启动JVM参数

-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

其中,参数-XX:PretenureSizeThreshold,参数要设置大对象阈值为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的数组,并且在第4个数组的时候,会因为新生代内存不足触发YGC。

此时内存分配如下:

image

对应如下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区。

那么大小为1MB的数组对象去哪里呢?肯定不是这个奇怪的512KB的对象。

这1MB的数组首先肯定是准备进入Survivor From区,可是,在我们设置的JVM参数下,只有0.5MB,明显是不够分配的。根据JVM YoungGC的规则,Survivor区放不下GC之后存活的对象,直接进入老年代

所以,1MB的数组对象是直接进入到老年代了。

此时,内存分配如下:

image

(2).代码块2

紧接这就是这块代码:

 array1 = new byte[1 * 1024 * 1024];

这里再次创建了3个1MB的数组对象,并且会触发一次YoungGC;

对应 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的数组对象去哪里呢?

这1MB的数组首先肯定是准备进入Survivor From区,可是,在我们设置的JVM参数下,只有0.5MB,明显是不够分配的。根据JVM YoungGC的规则,Survivor区放不下GC之后存活的对象,直接进入老年代

所以,1MB的数组对象是直接进入到老年代了。

之前看到的未知的对象512KB也进入到老年代,此时内存分配如下:

image.gif

(3).代码块3

array3 = null;

这里再次创建了3个1MB的数组对象,并且会触发一次YoungGC;

对应的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]

此时内存分配如下:

image

(4).代码块4

array2 = null;

这里再次创建了3个1MB的数组对象,并且会触发一次YoungGC;并且在这儿,触发Young GC之前触发了一次CMS的Old GC,触发的条件就是老年代可用空间小于了历次Young GC后升入老年代的对象的平均大小。此时新生代大小变成0KB

对应的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] 

此时内存分配如下:

image

(5).代码块5

array1 = null;

此时,再创建3个1MB的数组对象,再次触发一次Young GC,执行完YoungGC,此时新生代大小变成0KB;

对应的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]

此时内存分配如下:

image

(6).总结

如下GC堆内存日志我们也可以去验证下上面的推测:

此时新生代使用了53%的大小,我们还有一个1MB的数组,可能还存在一些未知对象。

在老年代中使用了大约3MB的空间,应该就是上图中的对象。

Heap

3.几个触发Full GC的条件

第一:是老年代可用内存小于新生代全部对象的大小,如果没开启空间担保参数,会直接触发Full GC,所以一般空间担保参数都会打开;注:jDK1.8之后已经取消了-XX:-HandlePromotionFailure机制

第二:是老年代可用内存小于历次新生代GC后进入老年代的平均对象大小,此时会提前Full GC;

第三:是新生代Minor GC后的存活对象大于Survivor,那么就会进入老年代,此时老年代内存不足。

上述情况都会导致老年代Full GC。

第四:就是“-XX:CMSInitiatingOccupancyFaction”参数,

如果老年代可用内存大于历次新生代GC后进入老年代的对象平均大小,但是老年代已经使用的内存空间超过了这个参数指定的比例,也会自动触发Full GC。默认92%

最后谢谢大家阅读,JVM之复杂,是要用头发丝作为代价去丈量的。向JVM冲刺,离JVM近点,我的头发丝就少点。文中若有错误,欢迎各位指正,谢谢大家。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • 作者:一字马胡 转载标志 【2017-11-12】 更新日志 日期更新内容备注 2017-11-12新建文章初版 ...
    beneke阅读 2,184评论 0 7
  • JVM架构 当一个程序启动之前,它的class会被类装载器装入方法区(Permanent区),执行引擎读取方法区的...
    cocohaifang阅读 1,644评论 0 7
  • 0. 前言 JVM笔记系列,以JDK1.7为基准,主要以《深入理解Java虚拟机》(第二版)和《Java虚拟机规范...
    郭寻抚阅读 887评论 0 3
  • Java 虚拟机有自己完善的硬件架构, 如处理器、堆栈、寄存器等,还具有相应的指令系统。JVM 屏蔽了与具体操作系...
    尹小凯阅读 1,678评论 0 10
  • 真空的自己在和别人交流的过程中,没有达到我预期的结果。反而陷入了自我怀疑中。我真的不能和周围的人相处吗,那样我的生...
    法桐阅读 175评论 0 2