CMS Garbage Collector

在CMS(Concurrent Mark Sweep)之前的Parallel垃圾回收器重点关注的是系统的吞吐量。但是并不是所有的应用的关注点都在吞吐量上,某些应用,要求低延迟,及时响应用户的请求,并且不会出现过长时间的stop-the-world(过长的stop-the-world给用户带来不好的体验),CMS就是为了这个需求而设计的。

1、CMS的堆结构


和其他复制算法一样,CMS也将新生代(Young Generation)分为了Eden和Survivor区,同时也拥有老年代(Old Generation)。

2、CMS的新生代回收


灰色区域代表的是未分配的区域,绿色表示新生代已经占用,蓝色表示老年代占用,深蓝表示刚刚从新生代复制到老年代,深绿表示刚刚从Eden区和一个Survivor区复制到另一个Survivor区。
而CMS中的新生代采用的算法是ParNew,也就是Serial的多线程版本。

由于Survivor区大小有限,因此当Eden和某个Survivor(Survivor1)存活下来的对象超过另一个Survivor(Survivor2)区的容量时,这个Survivor1中的部分对象会被移动到老年代中(深蓝色)。而新生代其他存活下来的对象会被移动到Suvivor2(深绿色)中。
IBA

3、CMS的老年代回收

而CMS与其他垃圾回收算法最大的不同就是老年代的回收,之所以没有很长的暂停,就是因为CMS将原来的长暂停分为了两个较短的暂停(CMS initial Mark 和 CMS remark),同时加入了并发标记阶段(CMS concurrent mark)。

下面的图是还没有开始Concurrent Sweep时堆的状态图,



下面来看看CMS主要有哪几个阶段。

1)CMS initial mark

这个阶段会标记从根对象(GC Root)直接可达的对象,而不是标记所有存活对象,这样就是为什么这个阶段速度很快。这个阶段会导致stop-the-world。

2)CMS concurrent mark

这个阶段中,GC线程和工作线程(worker threads)并发运行。如果CMS initial mark阶段标记的对象集合是A,那么CMS concurrent mark会针对集合A中的每个对象,标记从这些对象可以到达的所有对象。而由于GC线程和工作线程并发运行,所以不会导致stop-the-world。

3)CMS concurrent preclean

如果在CMS concurrent mark阶段,系统频繁的更新对象和产生新的对象,并且这些对象都要在下面的CMS remark阶段进行标记的话,会导致CMS remark阶段时间较长,而我们知道CMS remark阶段会导致stop-the-world,这就违反了我们设计CMS的初衷。所以引入CMS concurrent preclean阶段,用于标记那些在CMS concurrent mark阶段更新的和新生成的对象。这个阶段会反复运行多次,知道Eden区的占用率达到一个阈值。同上面的CMS concurrent mark一样,这个阶段也不会导致stop-the-world。

4)CMS remark

由于在CMS concurrent preclean阶段还会有对象被更新和生成新的对象,所以还需要CMS remark阶段再进行一次标记。这次标记会从根对象(GC Root)开始重新比较。这个阶段会处理 reference objects(soft and weak references)。这个阶段会导致stop-the-world。

5)CMS concurrent sweep

该阶段会在OOP(Ordinary Object Pointer)表中查找出dead对象,OOP表存放了所有对象的引用。然后将分配给这些dead对象的内存重新添加到free-list中,供生成新对象使用。这个阶段不会导致stop-the-world。





4、CMS缺点

1)无法收集浮动垃圾(floating garbage)。什么是浮动垃圾呢?在Concurrent sweep阶段,伴随着程序的运行还会产生新的垃圾,这些垃圾就是浮动垃圾。在本次收集中无法处理这些垃圾,只能等到下次垃圾回收。

2)碎片的产生,由于老年代采用标记-清除算法,因此会导致大量的碎片。在这种情况下,即使老年代还有大量的空间,可能会出现无法找到足够的连续空间存放新生成的对象。会导致一次full gc。

而G1垃圾回收器针对这个问题作了改进,采用标记-整理(Mark-Compact)算法。

3)CMS对CPU敏感。CMS默认启动的回收线程数是(CPU + 3) /  4,当CPU数量很少时(比如2),系统需要分配相当比例的资源给CMS,会导致应用程序(工作线程)变慢。

CPU为2时,GC线程数量为1,假设分配一个CPU执行这个线程,那么GC就占用了CPU的50%资源。而如果CPU大于4,比如CPU=8,那么GC线程为2,GC占用了CPU的资源也就是在25%左右,还有足够的资源分配给应用程序(工作线程)。
JVM垃圾回收器(GC)抖动(thrashing)是指JVM频繁进行垃圾回收,但每次回收释放的内存却非常有限,这通常会导致系统性能严重下降。在某些情况下,GC抖动可能会导致守护进程(daemon)超时甚至被操作系统终止(expiration),因为JVM长时间无法响应外部请求或心跳检测失败。 ### GC抖动的原因 1. **堆内存不足**:JVM堆内存配置过小,无法容纳程序运行时所需的对象,导致频繁GC。 2. **内存泄漏**:程序中存在未释放的无用对象引用,导致老年代(Old Generation)不断增长,最终触发Full GC。 3. **对象生命周期不合理**:大量短生命周期对象晋升到老年代(例如由于Survivor空间不足),导致老年代快速填满。 4. **GC算法选择不当**:不同的GC算法对不同类型的应用表现不同。例如,CMS(Concurrent Mark-Sweep)适用于低延迟场景,而G1(Garbage-First)则更适用于大堆内存管理。 5. **元空间(Metaspace)溢出**:类加载过多,导致元空间不断扩展,触发频繁的元空间GC。 ### GC抖动的表现 - **GC频率显著增加**:通过JVM监控工具(如JVisualVM、JConsole或Prometheus+Grafana)可以观察到GC事件频繁发生。 - **GC停顿时间增长**:尤其是Full GC的停顿时间显著增加,影响系统响应能力。 - **CPU使用率升高**:GC线程频繁运行,占用大量CPU资源。 - **守护进程超时**:例如Kubernetes中的Pod因健康检查失败而被重启,或ZooKeeper中的会话超时。 ### 解决方案 #### 1. 增加堆内存 适当增加JVM堆内存大小,尤其是老年代的大小,可以减少Full GC的频率。可以通过以下JVM参数调整堆大小: ```bash -Xms4g -Xmx8g -XX:NewRatio=2 ``` 上述配置将堆初始大小设为4GB,最大扩展到8GB,新生代与老年代的比例为1:2[^1]。 #### 2. 优化GC策略 根据应用类型选择合适的垃圾回收器: - **吞吐量优先**:Parallel Scavenge + Parallel Old - **低延迟优先**:G1 GC 或 ZGC 启用G1 GC的示例参数: ```bash -XX:+UseG1GC -XX:MaxGCPauseMillis=200 ``` 该配置启用G1垃圾回收器,并设置最大GC停顿时间为200毫秒[^1]。 #### 3. 检查内存泄漏 使用工具如Eclipse MAT(Memory Analyzer)分析堆转储(heap dump),查找未被释放的对象路径,尤其是`ClassLoader`、`ThreadLocal`、静态集合等常见泄漏源。 #### 4. 调整对象生命周期 减少短生命周期对象的晋升,可以通过调整Survivor空间大小或Tenuring阈值来实现: ```bash -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15 ``` #### 5. 监控和调优元空间 限制元空间最大使用量,防止其无限制增长: ```bash -XX:MaxMetaspaceSize=512m ``` 同时,可以通过`jstat -gc`命令监控元空间使用情况。 #### 6. 避免守护进程超时 在容器环境中(如Kubernetes),应合理设置健康检查(liveness/readiness probes)的超时时间和重试次数,避免因短暂GC停顿导致进程被误杀。 --- ### 示例:G1 GC日志分析片段 ```log 2024-07-01T12:00:00.000+0800: [GC pause (G1 Evacuation Pause) (young), 0.0561234 secs] [Parallel Time: 52.3 ms, GC Workers: 8] [Marking Phase: 12.4 ms] [Evacuation: 35.2 ms] [Other: 4.7 ms] ``` 上述日志显示一次G1年轻代GC耗时约56毫秒,属于正常范围。若频繁出现超过200毫秒的GC事件,则应引起关注。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值