JVM 并发标记清除:性能调优技巧

作者:袖梨 2026-07-08
JVM没有独立的“并发标记清除”收集器,而是CMS、G1等收集器内部采用的算法逻辑;应优先选对收集器(如G1为默认,ZGC/Shenandoah适配超低延迟),再通过参数优化标记与清除效率。

JVM 没有“并发标记清除”这一独立可启用的收集器或参数,它只是某些垃圾收集器(如已废弃的 CMS、或 G1 的部分回收阶段)内部采用的算法逻辑。真正能调优的,是选择合适收集器并配置其行为参数,从而间接优化标记与清除过程的并发性、延迟和碎片控制。

选对收集器,比调参更重要

CMS 在 JDK 9+ 已被移除,G1 是当前主流默认收集器(JDK 9 起),ZGC/Shenandoah 更适合超低延迟场景。不要试图“启用并发标记清除”,而应明确目标:

  • 若追求停顿可控(
  • 若需平衡吞吐与延迟(堆 4–64GB),G1 是更稳妥的选择;
  • 避免继续使用 CMS——它无压缩阶段,长期运行必然积累碎片,最终触发 Full GC,反而放大 STW 时间。

控制标记启动时机,防早不防晚

标记阶段是否及时启动,直接影响清除压力和内存碎片风险:

  • G1 中用 -XX:InitiatingHeapOccupancyPercent=35~45(默认45),设太低会频繁 Mixed GC,设太高易触发 Full GC;
  • CMS(仅限 JDK 8 及以前)用 -XX:CMSInitiatingOccupancyFraction=65~75,需结合老年代实际增长速率实测调整;
  • 配合 -XX:+PrintGCDetails -Xloggc:gc.log 观察老年代占用曲线,让标记在稳定增长期前触发,而非等临界点才动作。

分配策略减少无效标记负担

很多“标记慢”,本质是老年代塞了不该进的对象。从源头控制:

  • -XX:MaxTenuringThreshold=6(而非默认15),避免短寿对象在 Survivor 区反复拷贝后误升老年代;
  • 大对象(如 >2MB 的 byte[])直接进老年代:加 -XX:PretenureSizeThreshold=2097152(单位字节),避开新生代复制开销和老年代碎片化双重损耗;
  • 调小新生代比例(如 -XX:NewRatio=3)可缓解老年代过快填满,但需同步观察 Young GC 频率,避免 YGC 过于频繁。

降低标记与清除的实际开销

即使算法逻辑固定,运行时效率仍可优化:

  • 开启压缩指针:-XX:+UseCompressedOops(默认开启,确认未被禁用),减小对象引用体积,加快根扫描与对象遍历;
  • 为并发标记分配足够线程:-XX:ConcGCThreads=4(建议设为 CPU 核数的 1/4,上限一般 ≤8),太少导致标记拖尾,太多抢应用线程资源;
  • G1 下可启用自适应 IHOP:-XX:+G1UseAdaptiveIHOP(默认开启),让 JVM 根据历史 GC 数据动态调整标记启动阈值,比静态配置更稳。

不复杂但容易忽略:标记-清除不是开关,而是结果;调优的关键,在于让标记更准、更轻、更及时,让清除动作尽量少发生、少留碎片。

相关文章

精彩推荐