Java虚拟机:串行回收器
在 JVM 的垃圾回收器家族中串行回收器Serial GC是最古老、最基础的一位成员。虽然它不像 G1、ZGC 那样“高大上”但正是它奠定了现代垃圾回收的基石。理解 Serial GC不仅有助于我们应对老系统的性能问题更能帮助我们深刻理解 GC 的设计思想。本文将带你从工作原理、回收算法、日志解读、参数配置到应用场景全方位剖析 Serial GC。一、什么是串行回收器串行回收器是 JDK 中最基本的垃圾回收器之一它的核心特点可以用两个词概括单线程和独占式。1.1 单线程回收串行回收器在进行垃圾回收时仅使用一个线程来完成所有 GC 工作。这意味着没有多线程协作的复杂逻辑没有线程切换的开销实现简单执行高效1.2 独占式回收Stop-The-World串行回收器是独占式的——当它开始工作时Java 应用程序中的所有用户线程都必须完全暂停等待 GC 完成。这个过程被称为Stop-The-World简称 STW。在 GC 期间所有业务线程都被“冻结”这对实时性要求较高的系统来说可能是不可接受的。但在某些场景下这种“简单粗暴”的方式反而带来了最佳性能。二、新生代串行回收器Serial Young GC2.1 算法复制算法新生代串行回收器采用复制算法Copying Algorithm。其核心思想是将新生代内存划分为一个较大的Eden 区和两个较小的Survivor 区From 和 To。每次 GC 时将 Eden 和 From 中存活的对象复制到 To 区然后清空 Eden 和 From最后交换 From 和 To 的角色。为什么用复制算法新生代对象朝生夕死存活率极低复制算法的效率与存活对象数量成正比非常适合新生代实现简单没有内存碎片问题2.2 优势与局限优势局限实现简单逻辑高效单线程无法利用多核 CPU无线程切换开销GC 期间必须 STW在单 CPU 环境下性能优异堆内存较大时停顿时间过长2.3 参数配置-XX:UseSerialGC使用该参数后JVM 会同时启用新生代Serial 收集器使用复制算法老年代Serial Old 收集器使用标记-压缩算法在Client 模式下Serial GC 是 JVM 的默认垃圾收集器。三、老年代串行回收器Serial Old GC3.1 算法标记-压缩算法老年代串行回收器采用标记-压缩算法Mark-Compact Algorithm。该算法分为三个阶段标记Mark从 GC Roots 出发标记所有存活对象压缩Compact将所有存活对象向一端移动使它们连续排列清理Sweep清理掉边界以外的内存空间为什么老年代不用复制算法老年代对象存活率高复制成本太高标记-压缩算法避免了内存碎片且不需要额外空间3.2 注意老年代 GC 停顿更长由于老年代空间通常更大且存活对象更多Full GC 的停顿时间往往远超 Young GC。在堆内存较大的应用中一次 Serial Old GC 可能导致秒级甚至更长的停顿这在生产环境中需要特别警惕。3.3 参数配置# 方式一新生代和老年代都使用串行 -XX:UseSerialGC # 方式二新生代使用 ParNew老年代使用串行 -XX:UseParNewGC # 方式三新生代使用 ParallelGC老年代使用串行 -XX:UseParallelGC四、GC 日志深度解读4.1 新生代 GC 日志[GC [DefNew: 310K-194K(2368K), 0.0269163 secs] 310K-194K(7680K), 0.0269513 secs] [Times: user0.00 sys0.00, real0.03 secs]我们来逐段拆解日志片段含义GC表示这是一次 Young GC而非 Full GC[DefNew表示 GC 发生在新生代Default New Generation这是 Serial GC 特有的名称310K-194K(2368K)新生代GC前已使用→GC后已使用该区域总容量310K-194K(7680K)整个堆GC前已使用→GC后已使用堆总容量0.0269163 secs该区域 GC 耗时[Times: user0.00 sys0.00 real0.03 secs]见下方详解Times 字段详解userGC 线程消耗的 CPU 时间sys操作系统调用及等待系统事件的时间real应用程序实际暂停的时间挂钟时间对于 Serial GC由于只使用单线程real ≈ user sys。4.2 老年代 GCFull GC日志25.299: [Full GC (Allocation Failure) 25.300: [Tenured: 126975K-84388K(126976K), 0.1865275 secs] 741375K-84388K(741376K), [Metaspace: 3476K-3476K(1056768K)], 0.1866490 secs] [Times: user0.19 sys0.00, real0.18 secs]日志片段含义Full GC表示这是一次 Full GC(Allocation Failure)GC 触发原因分配对象失败内存不足[Tenured老年代Tenured Generation的 GC 信息126975K-84388K(126976K)老年代GC前→GC后总容量741375K-84388K(741376K)整个堆GC前→GC后总容量Metaspace: 3476K-3476K(1056768K)元空间信息JDK 84.3 不同收集器的新生代名称对照收集器新生代日志名称含义Serial[DefNewDefault New GenerationParNew[ParNewParallel New GenerationParallel Scavenge[PSYoungGenParallel Scavenge Young Generation五、示例代码与运行验证下面我们用一段代码来模拟内存溢出观察 Serial GC 的工作过程import java.util.Random; public class GCDemo { public static void main(String[] args) { System.out.println( GCDemo, Hello ); try { String str GCDemo; while (true) { str str new Random().nextInt(77777777) new Random().nextInt(88888888); str.intern(); } } catch (Throwable e) { e.printStackTrace(); } } }运行参数-XX:UseSerialGC -Xms10m -Xmx10m -XX:PrintGCDetails输出日志示例[GC [DefNew: 1393K-25K(3072K), 0.009758 secs] 5793K-5793K(9920K), 0.0010059 secs] [Full GC (Allocation Failure) [Tenured: 5767K-4167K(6948K), 0.0023995 secs] ... java.lang.OutOfMemoryError: Java heap space at java.util.Arrays.copyOf(Arrays.java:3332) at java.lang.AbstractStringBuilder.ensureCapacityInternal(...)可以看到DefNew确认了 Serial GC 在新生代工作Tenured确认了 Serial Old 在老年代工作频繁的 GC 最终导致OutOfMemoryError六、总结Serial GC 的优缺点与应用场景6.1 优点✅实现简单逻辑高效— 代码成熟稳定几乎没有复杂 bug✅单线程无交互开销— 没有多线程锁竞争和上下文切换✅在单 CPU 环境下性能最佳— 多线程反而会带来额外开销✅内存占用小— 不需要维护复杂的数据结构6.2 缺点❌STW 停顿时间长— 堆内存越大停顿越明显❌无法利用多核 CPU— 在多核时代显得有些“落伍”❌用户体验差— 在交互式应用中停顿可能导致卡顿6.3 适用场景场景是否适用原因Client 模式下的 JVM✅ 默认选择资源受限环境简单高效单 CPU 服务器✅ 最佳选择多线程无法带来收益堆内存 100MB✅ 可接受停顿时间可控实时 Web 应用❌ 不推荐停顿影响用户体验大堆内存4GB❌ 不推荐停顿时间过长