「译文」Java 垃圾收集参考手册(四):Serial GC
本文最后更新于:2024年7月25日 下午
Serial GC (串行 GC)
Serial GC 对年轻代使用 mark-copy (标记 - 复制) 算法 , 对老年代使用 mark-sweep-compact (标记 - 清除 - 整理) 算法. 顾名思义,两者都是单线程的垃圾收集器,不能进行并行处理。两者都会触发全线暂停 (STW), 停止所有的应用线程。
因此这种 GC 算法不能充分利用多核 CPU。不管有多少 CPU 内核,JVM 在垃圾收集时都只能使用单个核心。
要启用此款收集器,只需要指定一个 JVM 启动参数即可,同时对年轻代和老年代生效:
1 | |
该选项只适合几百 MB 堆内存的 JVM, 而且是单核 CPU 时比较有用。 对于服务器端来说,因为一般是多个 CPU 内核,并不推荐使用,除非确实需要限制 JVM 所使用的资源。大多数服务器端应用部署在多核平台上,选择 Serial GC 就表示人为的限制系统资源的使用。 导致的就是资源闲置,多的 CPU 资源也不能用来降低延迟,也不能用来增加吞吐量。
下面让我们看看 Serial GC 的垃圾收集日志,并从中提取什么有用的信息。为了打开 GC 日志记录,我们使用下面的 JVM 启动参数:
1 | |
产生的 GC 日志输出类似这样 (为了排版,已手工折行):
1 | |
此 GC 日志片段展示了在 JVM 中发生的很多事情。 实际上,在这段日志中产生了两个 GC 事件,其中一次清理的是年轻代,另一次清理的是整个堆内存。让我们先来分析前一次 GC, 其在年轻代中产生。
Minor GC (小型 GC)
以下代码片段展示了清理年轻代内存的 GC 事件:
2015-05-26T14:45:37.987-02001 :151.12622 : [GC3 (Allocation Failure4 151.126:
[DefNew5 :629119K->69888K6(629120K)7 , 0.0584157 secs]1619346K->1273247K8
(2027264K)9,0.0585007 secs10][Times: user=0.06 sys=0.00, real=0.06 secs]11
2015-05-26T14:45:37.987-0200– GC 事件开始的时间。其中-0200表示西二时区,而中国所在的东 8 区为+0800。151.126– GC 事件开始时,相对于 JVM 启动时的间隔时间,单位是秒。GC– 用来区分 Minor GC 还是 Full GC 的标志。GC表明这是一次小型 GC(Minor GC)Allocation Failure– 触发 GC 的原因。本次 GC 事件,是由于年轻代中没有空间来存放新的数据结构引起的。DefNew– 垃圾收集器的名称。这个神秘的名字表示的是在年轻代中使用的:单线程,标记 - 复制 (mark-copy), 全线暂停 (STW) 垃圾收集器。629119K->69888K– 在垃圾收集之前和之后年轻代的使用量。(629120K)– 年轻代总的空间大小。1619346K->1273247K– 在垃圾收集之前和之后整个堆内存的使用情况。(2027264K)– 可用堆的总空间大小。0.0585007 secs– GC 事件持续的时间,以秒为单位。[Times: user=0.06 sys=0.00, real=0.06 secs]– GC 事件的持续时间,通过三个部分来衡量:
- user – 在此次垃圾回收过程中,所有 GC 线程所消耗的 CPU 时间之和。
- sys – GC 过程中中操作系统调用和系统等待事件所消耗的时间。
- real – 应用程序暂停的时间。因为串行垃圾收集器 (Serial Garbage Collector) 只使用单线程,因此 real time 等于 user 和 system 时间的总和。
可以从上面的日志片段了解到,在 GC 事件中,JVM 的内存使用情况发生了怎样的变化。此次垃圾收集之前,堆内存总的使用量为 1,619,346K。其中,年轻代使用了 629,119K。可以算出,老年代使用量为 990,227K。
更重要的信息蕴含在下一批数字中,垃圾收集之后,年轻代的使用量减少了 559,231K, 但堆内存的总体使用量只下降了 346,099K。 从中可以算出,有 213,132K 的对象从年轻代提升到了老年代。
此次 GC 事件也可以用下面的示意图来说明,显示的是 GC 开始之前,以及刚刚结束之后,这两个时间点内存使用情况的快照:

Full GC (完全 GC)
理解第一次 minor GC 事件后,让我们看看日志中的第二次 GC 事件:
2015-05-26T14:45:59.690-02001 :172.8292 : [GC (Allocation Failure 172.829:
[DefNew: 629120K->629120K(629120K), 0.0000372 secs3] 172.829:[Tenured4:
1203359K->755802K5(1398144K)6,0.1855567 secs7 ]1832479K->755802K8
(2027264K)9,[Metaspace: 6741K->6741K(1056768K)]10
[Times: user=0.18 sys=0.00, real=0.18 secs]11
2015-05-26T14:45:59.690-0200– GC 事件开始的时间。其中-0200表示西二时区,而中国所在的东 8 区为+0800。172.829– GC 事件开始时,相对于 JVM 启动时的间隔时间,单位是秒。[DefNew: 629120K->629120K(629120K), 0.0000372 secs– 与上面的示例类似,因为内存分配失败,在年轻代中发生了一次 minor GC。此次 GC 同样使用的是 DefNew 收集器,让年轻代的使用量从 629120K 降为 0。注意,JVM 对此次 GC 的报告有些问题,误将年轻代认为是完全填满的。此次垃圾收集消耗了 0.0000372 秒。Tenured– 用于清理老年代空间的垃圾收集器名称。Tenured 表明使用的是单线程的全线暂停垃圾收集器,收集算法为 标记 - 清除 - 整理 (mark-sweep-compact)。1203359K->755802K– 在垃圾收集之前和之后老年代的使用量。(1398144K)– 老年代的总空间大小。0.1855567 secs– 清理老年代所花的时间。1832479K->755802K– 在垃圾收集之前和之后,整个堆内存的使用情况。(2027264K)– 可用堆的总空间大小。[Metaspace: 6741K->6741K(1056768K)]– 关于 Metaspace 空间,同样的信息。可以看出,此次 GC 过程中 Metaspace 中没有收集到任何垃圾。[Times: user=0.18 sys=0.00, real=0.18 secs]– GC 事件的持续时间,通过三个部分来衡量:
- user – 在此次垃圾回收过程中,所有 GC 线程所消耗的 CPU 时间之和
- sys – GC 过程中中操作系统调用和系统等待事件所消耗的时间。
- real – 应用程序暂停的时间。因为串行垃圾收集器 (Serial Garbage Collector) 只使用单线程,因此 real time 等于 user 和 system 时间的总和。
和 Minor GC 相比,最明显的区别是 —— 在此次 GC 事件中,除了年轻代,还清理了老年代和 Metaspace. 在 GC 事件开始之前,以及刚刚结束之后的内存布局,可以用下面的示意图来说明:
