为何在使用CMS gc算法时会出现连续两次full gc

本文通过jstat和gclog分析了CMS垃圾回收过程中initial-mark和remark阶段导致的Full GC情况,并解释了停顿的原因。

现象:

jstat -gcutil pid 1000观察到的情况,段时间内连续两次full gc

  S0     S1     E      O      P     YGC     YGCT    FGC    FGCT     GCT 
59.33   0.00  63.98  69.66  59.31  24338  274.969   307   17.349  292.318
 59.33   0.00  86.19  69.66  59.31  24338  274.969   307   17.349  292.318
  0.00  60.52  10.22  70.10  59.31  24339  275.006   308   17.373  292.379
  0.00  60.52  30.03  70.10  59.31  24339  275.006   308   17.373  292.379
  0.00  60.52  53.57  70.10  59.31  24339  275.006   308   17.373  292.379
  0.00  60.52  76.45  70.10  59.31  24339  275.006   308   17.373  292.379
  0.00  60.52  93.77  70.10  59.31  24339  275.006   308   17.373  292.379
 61.16   0.00  15.66  70.53  59.31  24340  275.040   308   17.373  292.413
 61.16   0.00  40.71  67.96  59.31  24340  275.040   309   17.399  292.439
 61.16   0.00  66.44  59.90  59.31  24340  275.040   309   17.399  292.439

 同一时间的gc log

第一次

[GC [1 CMS-initial-mark: 1220647K(1740800K)] 1227199K(2385920K), 0.0057570 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

在旧生代空间为1220647K触发marking操作,后面1227199K(2385920K)=当前总体jvm内存使用(maxMem)

initial  标记从根集合中可直接访问的对象,要停顿整个应用

原文:This is initial Marking phase of CMS where all the objects directly reachable from roots are marked and this is done with all the mutator threads stopped.

[CMS-concurrent-mark: 1.084/1.084 secs] [Times: user=2.43 sys=0.11, real=1.08 secs]

完成mark耗时1.084 secs 并发执行

[CMS-concurrent-preclean: 0.008/0.009 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]

开始执行清理,目的是减少remark的时间

 

第二次

[GC [ParNew: 579913K->4387K(645120K), 0.0080390 secs] 1800561K->1226923K(2385920K), 0.0082860 secs] [Times: user=0.05 sys=0.00, real=0.01 secs]

minor gc

  CMS: abort preclean due to time [CMS-concurrent-abortable-preclean: 2.715/5.047 secs] [Times: user=4.42 sys=0.40, real=5.05 secs]

停止执行preclean 默认是eden到达50%或者real time=5secs停止

[GC[YG occupancy: 153139 K (645120 K)][Rescan (parallel) , 0.0348570 secs][weak refs processing, 0.0005070 secs] [1 CMS-remark: 1222535K(1740800K)] 1375675K(2385920K), 0.0355150 secs] [Times: user=0.19 sys=0.00, real=0.04 secs]

remark会停顿整个应用

[CMS-concurrent-sweep: 1.358/1.360 secs] [Times: user=1.85 sys=0.10, real=1.36 secs]

[CMS-concurrent-reset: 0.003/0.003 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

gcutil 看到连续两次 full gc

原因分析:

目前来看,只有当initial-mark和remark时才会停顿整个应用,这个两个时间点jstat -gcutil表现都为full gc次数加一

 

real != user+sys

原因:user和sys代表处于用户和系统态的时间,不包含block的时间.但是由于多cpus这个东东是累加的.所以一般来说user和sys相加都大于real

real是从启动到终止的真实时间(现实中消耗),包含block.

----

CMS-initial-mark 对应一次fullgc,会停顿所有线程

CMS-remark 对应依次fullgc,会停顿所有线程

其他的日志对应的操作都是不会停顿线程的

可以通过grep "initial" 和grep "remark" 来看你每次停顿的时间

 

### CMS GC 中触发 Full GC 的原因及机制 #### 原因分析 在CMS(Concurrent Mark-Sweep)垃圾收集器的工作过程中,当老年代的空间不足以容纳新晋升的对象时,可能会触发Full GC。具体来说: - 当执行CMS GC的过程中,如果存在对象需要从年轻代晋升至老年代,而此时老年代空间不足,则会引发Full GC操作[^1]。 - 如果在CMS GC运行期间,由于浮动垃圾的存在导致临时性的空间不足,也可能触发Full GC来清理这些垃圾并释放足够的空间。 另外,在某些情况下,即使Survivor Space有足够的容量用于存储存活下来的对象,但如果某个对象的大小超过了To Space的最大可用内存,那么此对象会被直接转移到老年代;一旦老年代无法提供相应的连续内存块给这样的大对象,也会造成Full GC的发生[(5)](https://docs.oracle.com/javase/8/docs/api/java/lang/System.html#gc--) [^2]. #### 触发机制详解 以下是几种可能导致CMS环境下发生Full GC的具体场景及其背后的触发机制: 1. **分配担保失败**: 在Minor GC完成后尝试将部分幸存者迁移到老年代之前,JVM会对目标区域的老年份剩余空间做预估计算。假如预测结果显示没有足够多的自由空间可供安置即将迁移过来的新成员们的话,就会启动一次完整的全局扫描过程——也就是所谓的Full GC动作来进行必要的调整优化工作直至满足需求为止[^3]. 2. **元数据溢出(Metaspace Out Of Memory)**: 尽管自JDK 7u40之后引入了Metaspace概念替代原有的PermGen设计模式从而减少了永久代满载的风险程度;然而仍然有可能因为加载类数量过多或者单个类定义过大等原因最终耗尽整个堆外专属分区资源进而迫使系统发起一轮全面清扫行动以腾挪更多宝贵位置供后续使用. 3. **显式调用System.gc()方法**: Java程序开发者有时出于调试目的或者其他特殊考量主动发出请求让虚拟机尽快安排一场大规模整理活动通过这种方式可以间接促使原本计划中的增量型维护转变为即时生效的整体性重构方案尽管现代版本已经弱化甚至屏蔽掉这种人为干预手段的影响但仍需留意其潜在后果. 4. **并发标记阶段未能及时完成而导致串行处理介入**: 正常状况下,CMS算法采用分步交错方式进行各项子任务之间相互配合协作实现高效运作效果可是偶尔会出现个别极端案例使得预定时间内没能顺利完成预期进度于是不得不切换回传统方式重新开始新一轮更为彻底深入细致入微级别的排查整治流程这就是为什么我们常常能够观察到伴随着常规周期之外额外增加出来的几短暂停顿现象实际上正是上述转换所引起的结果表现形式之一而已. ```java // 示例代码展示如何手动触发改行为 (不推荐生产环境这么做!) public class ForceFullGCDemo { public static void main(String[] args) throws InterruptedException{ Runtime.getRuntime().exec("cmd /c start"); // 创建大量进程模拟压力测试 Thread.sleep(1000); System.out.println("Before calling System.gc()"); System.gc(); // 显式建议 JVM 执行 Full GC Thread.sleep(5000); System.out.println("After calling System.gc()"); } } ``` --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值