parallel gc本质是多线程独占式stw,所有用户线程暂停直至gc结束,以吞吐量优先为设计契约;组合为parallel scavenge+parallel old,适用批处理等延迟不敏感场景。

并行GC(Parallel GC)本质是“多线程独占式STW”
它用多个GC线程并行工作,但**所有用户线程必须暂停(Stop-the-World)**,直到本轮GC结束。这不是调度策略问题,而是设计契约:吞吐量优先,不妥协停顿时间。
常见组合是 Parallel Scavenge(新生代) + Parallel Old(老年代),JDK 8 默认、JDK 9–10 仍广泛使用。启用参数为:-XX:+UseParallelGC 或 -XX:+UseParallelOldGC。
- 适合后台批处理、离线计算等对响应延迟不敏感的场景
-
-XX:MaxGCPauseMillis是软目标,GC会尽力逼近,但不保证;设太小会导致频繁GC、吞吐量暴跌 - 默认GC线程数 = CPU核心数;可通过
-XX:ParallelGCThreads手动调小(比如容器里CPU限制为2核,却未改此值,会引发线程争抢) - 老年代回收仍用标记-压缩算法,会产生一定内存整理开销,但不会碎片化
并发GC(Concurrent GC)核心目标是“减少STW时长”
它让GC线程与用户线程**逻辑上同时运行**——不是物理并行(不一定占用独立CPU),而是通过细粒度任务拆分、写屏障、增量式扫描等机制,把原本集中的一次长停顿,拆成多次极短停顿(甚至部分阶段完全无STW)。
典型代表是 CMS(已废弃)、G1(JDK 9+ 默认)、ZGC(JDK 11+)、Shenandoah(JDK 12+)。启用如:-XX:+UseG1GC、-XX:+UseZGC。
-
CMS在 JDK 14 被彻底删除,别在新项目里配-XX:+UseConcMarkSweepGC -
G1的混合回收(Mixed GC)仍含STW,但停顿可控;其“并发标记”阶段大部分时间与应用线程交替执行 -
ZGC和Shenandoah把STW压到毫秒级(通常 - 并发GC普遍更吃CPU:写屏障插入、SATB缓冲区处理、并发引用更新都会增加用户线程开销
别混淆“并发”和“并行”的JVM参数命名
JVM参数里带 Conc 的(如 UseConcMarkSweepGC、UseZGC)基本都指并发GC;而带 Parallel 的(如 UseParallelGC、ParallelGCThreads)全属并行GC体系——哪怕名字里有“Conc”,也不代表它能并发运行。
一个经典陷阱:-XX:+UseParNewGC 听起来像“并发”,实则是 Serial 的多线程版,**仍是STW**,且从 JDK 9 起已被标记为废弃,G1不再支持它作为新生代收集器(G1自管新生代)。
-
ParNew和Parallel Scavenge都是并行、STW,但算法细节和自适应策略不同;后者更适合吞吐量敏感场景 - 查看当前生效的GC:启动时加
-XX:+PrintCommandLineFlags,比猜参数更可靠 - 容器环境(如Kubernetes)中,
ParallelGCThreads不会自动感知cgroup CPU quota,必须显式设置,否则可能超发线程
选型时真正要权衡的不是“并发 or 并行”,而是停顿 vs 吞吐 vs 内存
没有银弹。64GB堆、16核CPU的实时风控服务,用 Parallel GC 可能单次Full GC卡住3秒以上;换 ZGC 后,停顿压到0.8ms,但CPU使用率从35%升到65%——这得看你的SLA到底卡哪条线。
更隐蔽的坑在于:并发GC对代码行为更敏感。比如大量使用 java.lang.ref.WeakReference 或 PhantomReference,在 G1 下可能触发额外的并发引用处理阶段,反而拉高延迟;而 Parallel GC 对这类引用处理是批量、同步、可预测的。
真实压测前,先看GC日志里 pause 和 concurrent 字段的分布比例——很多团队以为自己用了“并发GC”,结果日志里全是 [GC pause (G1 Evacuation Pause)],根本没跑进并发标记阶段,纯属配置或负载没到位。











