news 2026/7/30 23:56:02

【JVM原理详解】25-Serial与ParNew收集器

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【JVM原理详解】25-Serial与ParNew收集器

25-Serial 与 ParNew 收集器

前面几篇讲的是"算法",从本篇开始进入"实现"——具体到 HotSpot 提供的几种垃圾收集器。最古老、也是最简单的就是SerialSerial Old收集器,它们是理解后续所有收集器的基础。ParNew则是 Serial 的多线程版本,专为配合 CMS 而生。本篇将剖析它们的设计、参数、组合关系与适用场景。

Serial 收集器

Serial 是 HotSpot 最古老的收集器:单线程回收,回收时必须 STW(Stop The World),期间所有应用线程挂起。它的新生代采用复制算法,老年代对应的是 Serial Old(Mark-Compact)。

应用线程: ████░░░░░░░░████████... ↑ Serial STW

设计哲学

单线程听起来"落后",但它的优势恰恰是简单

  • 没有线程同步、协调开销,单次 GC 的 CPU 利用率高。
  • 适合单核或小内存环境。

在 Client 模式或嵌入式设备上,Serial 是默认选择。即使在现代 JDK 中,它依然是新生代默认收集器候选(取决于平台和 JDK 版本)。

Serial Old

Serial Old 是 Serial 的老年代版本,同样单线程,采用 Mark-Compact 算法。它主要有两个用途:

  1. 在 Client 模式下与 Serial 配套。
  2. 作为 CMS 的"后备"——当 CMS 出现 Concurrent Mode Failure 时退化为 Serial Old 执行 Full GC。

第二种场景在 JDK 8 + CMS 的生产环境中是出了名的"长停顿"来源,也是 CMS 被废弃的重要原因。

关键参数

# 显式指定使用 Serial + Serial Oldjava-XX:+UseSerialGC-cpMyApp com.example.Main

-XX:+UseSerialGC等同于同时启用 Serial(新生代)与 Serial Old(老年代)。JDK 8 在 Client 模式下默认就是这套组合。

代码示例与日志分析

/** * 演示 Serial GC 行为 * 适用 JDK 8/11/17 * * 运行: * java -Xms20m -Xmx20m -Xmn10m * -XX:SurvivorRatio=8 -XX:+UseSerialGC * -Xlog:gc*=info -cp MyApp SerialGcDemo */publicclassSerialGcDemo{privatestaticfinalint_1MB=1024*1024;publicstaticvoidmain(String[]args)throwsException{for(inti=0;i<8;i++){byte[]block=newbyte[2*_1MB];System.out.println("allocated "+i+" -> "+block.length);Thread.sleep(300);}}}

JDK 11+ 日志(-Xlog:gc*=info):

[0.123s][info][gc,start] GC(0) Pause Young (Allocation Failure) [0.123s][info][gc,task] GC(0) Using 1 workers [0.124s][info][gc,heap] GC(0) DefNew total 9216K, used 8000K [0.124s][info][gc,heap] GC(0) Eden space 8192K, 100% used [0.124s][info][gc,heap] GC(0) From space 1024K, 0% used [0.124s][info][gc,heap] GC(0) To space 1024K, 0% used [0.124s][info][gc,heap] GC(0) Tenured generation total 10240K, used 0K [0.124s][info][gc] GC(0) Pause Young (Allocation Failure) 7M->1M(20M) 1.234ms

关注几个关键字段:

  • DefNew:Default New Generation,Serial 新生代的内部名。
  • Using 1 workers:单线程回收。
  • 7M->1M(20M):回收前 7M,回收后 1M,堆总大小 20M。
  • 1.234ms:本次 GC 停顿。

老年代 Full GC 日志:

[5.678s][info][gc,start] GC(5) Pause Full (Allocation Failure) [5.678s][info][gc,heap] GC(5) Tenured generation total 10240K, used 8000K [5.679s][info][gc] GC(5) Pause Full (Allocation Failure) 18M->12M(20M) 5.678ms

Tenured是 Serial Old 老年代名。可以看到 Full GC 的停顿比 Minor GC 长得多——这是 Mark-Compact 算法的固有代价。

ParNew 收集器

ParNew 是 Serial 的多线程版本:新生代复制算法、STW,但回收时用多个 GC 线程并行。它是唯一能与 CMS 配合的新生代收集器。

应用线程: ████░░░░░░░░████████... ↑ ParNew STW(多 GC 线程并行)

与 Serial 的关系

ParNew 在单核环境下不会比 Serial 更快——多线程协调反而有额外开销。但在多核环境下,多线程并行能显著缩短 STW 时间:

单线程: ████████████ (12ms) 4线程: ███ (3ms)

这也是"吞吐量 vs 延迟"的早期权衡:ParNew 通过并行降低单次停顿,但总 CPU 开销与 Serial 相当甚至略高。

关键参数

# JDK 8:显式启用 ParNew + CMSjava-XX:+UseParNewGC-XX:+UseConcMarkSweepGC-cpMyApp com.example.Main# 控制并行 GC 线程数java-XX:+UseParNewGC-XX:ParallelGCThreads=4-cpMyApp com.example.Main

ParallelGCThreads默认值与 CPU 核数相关:核数 ≤ 8 时等于核数,否则为3 + 5N/8(N 为核数)。在容器化部署中建议显式设置,避免误用宿主机核数。

ParNew 的局限

ParNew 只能回收新生代,必须与一个老年代收集器搭配。JDK 8 可用组合:

  • ParNew + CMS(推荐,低延迟)
  • ParNew + Serial Old(退化,不推荐)

JDK 9 起,由于 CMS 被标记 deprecated,ParNew 也被限制:-XX:+UseParNewGC单独使用会报警告,最终在 JDK 14 随 CMS 一起被移除。新代码应直接用 G1。

收集器组合关系

HotSpot 不同代际收集器的组合关系是有限制的。下表是 JDK 8 的合法组合:

新生代老年代说明
SerialSerial OldClient 模式、嵌入式
ParNewCMS低延迟首选
ParNewSerial Old不推荐,仅兼容性
Parallel ScavengeParallel Old吞吐量优先
G1(自身分代)JDK 9+ 默认

注意:ParNew + Parallel Old不是合法组合。原因是 ParNew 与 CMS 共享代码框架,Parallel Scavenge 走的是另一套实现。这种"组合限制"常常是面试题的考点,也是 JDK 9 后统一向 G1 收敛的内在动因。

选型决策树

堆 < 100MB → Serial Client/嵌入式 → Serial 吞吐量优先(批处理) → Parallel Scavenge + Parallel Old 低延迟(JDK 8) → ParNew + CMS 低延迟(JDK 11+) → G1(或 ZGC) 超低延迟(JDK 15+) → ZGC / Shenandoah

代码示例:对比 Serial 与 ParNew

下面这段代码在两种收集器下运行,对比 GC 日志差异:

/** * Serial vs ParNew 对比 * 适用 JDK 8 */publicclassCollectorCompare{privatestaticfinalint_1MB=1024*1024;publicstaticvoidmain(String[]args)throwsException{// 持续分配,制造 GC 压力for(inti=0;i<50;i++){byte[]block=newbyte[512*1024];Thread.sleep(50);}}}

运行命令:

# Serialjava-Xmx200m-Xmn100m-XX:+UseSerialGC\-Xlog:gc*=info-cpMyApp CollectorCompare# ParNew + CMSjava-Xmx200m-Xmn100m-XX:+UseParNewGC-XX:+UseConcMarkSweepGC\-Xlog:gc*=info-cpMyApp CollectorCompare

对比日志关键字段:

字段SerialParNew
新生代名DefNewParNew
GC 线程数Using 1 workersUsing 8 workers
单次停顿较长较短(多核时)

典型输出(8 核机器,200M 堆):

# Serial [0.045s][info][gc] GC(0) Pause Young (Allocation Failure) 60M->10M(200M) 4.321ms # ParNew [0.038s][info][gc] GC(0) Pause Young (Allocation Failure) 60M->10M(200M) 1.102ms

多核环境下 ParNew 的停顿显著低于 Serial。但在单核或极小堆上,Serial 因无线程协调开销可能反而更快。

适用场景

Serial / Serial Old

  • 嵌入式设备:内存小(数十 MB)、CPU 弱,单线程更高效。
  • Client 模式桌面应用:堆不大、对停顿不敏感。
  • 微服务预热阶段:某些框架在启动期用 Serial 减少开销。
  • 教学/调试:日志简单,便于理解 GC 原理。

ParNew

  • JDK 8 + CMS 的低延迟应用:Web 服务、交易系统等对 STW 敏感的场景。
  • 多核中小堆:堆在 4GB 以内时,ParNew + CMS 仍是合理选择。
  • JDK 9+ 不再推荐:应迁移到 G1。

迁移建议

如果你的系统还在用 ParNew + CMS:

  • JDK 11:切换到 G1,通常能获得相近或更好的延迟表现。
  • JDK 17+:评估 ZGC 或 Shenandoah,追求个位数毫秒级停顿。
  • 迁移前用 GC 日志工具(如 GCEasy)分析现有 GC 模式,作为基线对比。

实践要点

1.-XX:+UseParallelGC不等于 ParNew

JDK 8 中-XX:+UseParallelGC启用的是Parallel Scavenge + Parallel Old,不是 ParNew。两者代码不同、组合不互通。命名容易混淆,需特别注意。

2. 容器中的 GC 线程数

Docker/K8s 环境下,JVM 可能误识别宿主机核数,导致 GC 线程过多。建议显式设置:

java-XX:ParallelGCThreads=4-XX:ConcGCThreads=2-cpMyApp com.example.Main

JDK 10+ 的容器感知(-XX:+UseContainerSupport,默认开启)已能自动识别 cgroup 限制,但显式设置更稳妥。

3. 日志格式统一

JDK 9 引入统一日志格式(-Xlog:),JDK 8 用-XX:+PrintGCDetails。迁移时记得转换参数:

# JDK 8-XX:+PrintGCDetails-XX:+PrintGCDateStamps-Xloggc:gc.log# JDK 11+-Xlog:gc*=info:file=gc.log:time,uptime,level,tags

4. 小堆场景下 Serial 可能更优

堆小于 100MB 时,多线程协调的开销可能超过并行收益。这时-XX:+UseSerialGC反而更好。某些 IoT 场景就是如此。

5. GC 日志分析工具

  • GCEasy(gceasy.io):在线分析,给出停顿、吞吐量、内存分布等指标。
  • GCViewer:本地工具,适合离线分析。
  • JDK Mission Control(JMC):JDK 11+ 自带,可关联 GC 事件与应用行为。

小结

  • Serial / Serial Old:单线程、STW、实现简单,适合嵌入式和 Client 场景;新生代用复制算法,老年代用 Mark-Compact。
  • ParNew:Serial 的多线程版本,新生代复制算法,配合 CMS 使用;JDK 9 起逐步退出历史舞台。
  • 收集器组合有严格限制,ParNew 只能与 CMS / Serial Old 搭配,不能与 Parallel Old 组合。
  • 容器化部署应显式设置ParallelGCThreads,避免误用宿主机核数。
  • 现代应用建议直接使用 G1(JDK 9+ 默认)或 ZGC/Shenandoah(JDK 15+),Serial/ParNew 仅在特定小内存或历史系统中保留。

本模块至此完成了从"判定对象存活"到"基础回收算法"再到"早期收集器"的脉络。下一篇我们将进入更现代的收集器——Parallel Scavenge 与 CMS——继续探讨吞吐量与延迟的权衡。

更多内容:JVM调优实战

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/30 23:55:02

如何在Android应用中集成Countly SDK:5分钟快速上手教程

如何在Android应用中集成Countly SDK&#xff1a;5分钟快速上手教程 【免费下载链接】countly-sdk-android Countly Digital Analytics Android SDK 项目地址: https://gitcode.com/gh_mirrors/co/countly-sdk-android Countly Digital Analytics Android SDK是一款功能…

作者头像 李华
网站建设 2026/7/30 23:53:59

Codex任务中断的真实成本:ChatGPT Plus与Pro应该怎么选?

很多开发者第一次考虑从ChatGPT Plus升级到Pro&#xff0c;并不是因为模型回答不够聪明&#xff0c;而是因为Codex任务执行到一半时&#xff0c;使用额度突然不足。代码已经分析了一半&#xff0c;测试环境刚刚跑通&#xff0c;Agent也理解了项目结构&#xff0c;却无法继续执行…

作者头像 李华
网站建设 2026/7/30 23:53:06

google-font-download命令详解:轻松掌握所有参数与用法

google-font-download命令详解&#xff1a;轻松掌握所有参数与用法 【免费下载链接】google-font-download Locally host Googles web fonts 项目地址: https://gitcode.com/gh_mirrors/go/google-font-download google-font-download是一款实用的命令行工具&#xff0c…

作者头像 李华
网站建设 2026/7/30 23:52:47

实测案例 马路科技汇专超声方案突破半导体塑料芯片微孔加工瓶颈

芯片测试插座(Semiconductor Test Socket)是一种应用于半导体生产过程成品测试阶段的“消耗型”硬件&#xff0c;通常由探针、电子元件、线材与印刷电路板&#xff08;PCB&#xff09;组成&#xff0c;承担着晶圆与测试系统之间电连接的关键作用&#xff0c;能够对芯片进行缺陷…

作者头像 李华
网站建设 2026/7/30 23:51:07

3步搞定!tchMaterial-parser电子课本下载神器,让教学资源唾手可得

3步搞定&#xff01;tchMaterial-parser电子课本下载神器&#xff0c;让教学资源唾手可得 【免费下载链接】tchMaterial-parser 国家中小学智慧教育平台 电子课本下载工具&#xff0c;帮助您从智慧教育平台中获取电子课本的 PDF 文件网址并进行下载&#xff0c;让您更方便地获取…

作者头像 李华