作为一名长期奋战在JVM性能调优一线的工程师,我经常需要面对各种垃圾收集器选型和参数调优的挑战。今天我想和大家深入探讨ParNew、CMS这两款经典收集器的实现原理,以及它们底层依赖的三色标记算法。这些知识不仅是面试高频考点,更是解决实际生产环境GC问题的关键钥匙。
现代JVM垃圾收集的核心思想源于分代假设(Generational Hypothesis)。根据IBM研究表明,98%的Java对象存活时间不超过1毫秒。基于这个观察,HotSpot VM将堆内存划分为:
这种分区的优势在于可以针对不同生命周期的对象采用最优收集策略。在我的调优实践中,合理设置-XX:NewRatio参数(默认值2,即老年代是年轻代的2倍)对系统性能影响显著。
年轻代采用的复制算法将内存分为Eden区和两个Survivor区(默认比例8:1:1)。对象分配流程如下:
关键参数:-XX:MaxTenuringThreshold控制晋升阈值,-XX:SurvivorRatio调整Survivor区比例
我曾遇到一个案例:某电商系统在促销时频繁Full GC。分析发现Survivor区过小导致对象过早晋升,调整-XX:SurvivorRatio=6后,老年代压力降低70%。
老年代通常采用这两种算法:
标记-清除:先标记存活对象,然后清除未标记区域
标记-整理:标记后将所有存活对象向一端移动
在内存大于4GB的系统上,我通常建议开启-XX:+UseCMSCompactAtFullCollection,在Full GC时进行内存压缩。
作为Serial收集器的多线程版本,ParNew是许多Java应用的标准配置:
java复制// 典型JVM参数配置示例
-XX:+UseParNewGC
-XX:ParallelGCThreads=4 // 根据CPU核心数设置
工作特点:
在8核服务器上,我通常设置ParallelGCThreads为CPU核数的5/8,避免过多线程争抢资源。
Concurrent Mark Sweep收集器是JDK1.4引入的低延迟收集器,其核心设计目标是减少停顿时间:
初始标记(Initial Mark):STW阶段,仅标记GC Roots直接关联对象
并发标记(Concurrent Mark):与用户线程并发执行
重新标记(Remark):STW阶段,修正并发期间的引用变化
并发清除(Concurrent Sweep):清理垃圾对象
bash复制-XX:CMSInitiatingOccupancyFraction=75 # 老年代使用率触发阈值
-XX:+CMSScavengeBeforeRemark # 重新标记前先做Young GC
-XX:+CMSParallelRemarkEnabled # 并行重新标记
常见问题处理:
三色标记是CMS和G1等收集器的理论基础:
java复制// 伪代码示例
class Object {
Color color;
Object[] references;
void mark() {
if (color != WHITE) return;
color = GREY;
for (Object ref : references) {
ref.mark();
}
color = BLACK;
}
}
并发标记期间可能出现的对象图变化会导致漏标:
典型场景:
CMS采用的解决方案:
cpp复制// 写屏障伪代码
void post_write_barrier(Object* field, Object* new_val) {
if ($gc_phase == CONCURRENT_MARK) {
enqueue(new_val); // 记录新引用
}
*field = new_val;
}
G1采用的替代方案:
cpp复制void pre_write_barrier(Object* field, Object* old_val) {
if ($gc_phase == CONCURRENT_MARK && old_val != null) {
satb_queue.push(old_val); // 保存旧引用
}
}
对于8GB堆内存的Web服务,推荐配置:
bash复制-Xms8g -Xmx8g
-XX:+UseConcMarkSweepGC
-XX:CMSInitiatingOccupancyFraction=80
-XX:+CMSParallelRemarkEnabled
-XX:+CMSScavengeBeforeRemark
-XX:+UseCMSInitiatingOccupancyOnly
-XX:ParallelGCThreads=6
-XX:ConcGCThreads=4
注意事项:
案例1:周期性长时间停顿
案例2:并发模式失败
虽然CMS已在JDK14中被移除,但理解它的设计对掌握G1、ZGC仍有帮助:
| 特性 | CMS | G1 | ZGC |
|---|---|---|---|
| 算法 | 标记-清除 | 分区+标记整理 | 着色指针+读屏障 |
| 停顿目标 | 低延迟 | 可预测停顿 | 亚毫秒级停顿 |
| 内存占用 | 中等 | 较高(10-20%额外) | 高(TB级堆可用) |
| JDK支持 | 1.4-14 | 1.7+ | 11+ |
| 适用场景 | 中小堆内存 | 大堆内存 | 超大堆内存 |
在实际迁移过程中,从CMS切换到G1需要注意:
对于追求极致低延迟的系统,ZGC的Submillisecond pauses特性值得考虑,但需要JDK11+支持且内存占用更高。在我参与的某金融交易系统改造中,ZGC将99.9%的GC停顿控制在3ms以内,显著提升了高频交易性能。