「译文」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-0200
1 :151.1262
2 : [GC
3 (Allocation Failure
4 151.126:
[DefNew
5 :629119K->69888K
6(629120K)
7 , 0.0584157 secs]1619346K->1273247K
8
(2027264K)
9,0.0585007 secs
10][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-0200
1 :172.829
2 : [GC (Allocation Failure 172.829:
[DefNew: 629120K->629120K(629120K), 0.0000372 secs
3] 172.829:[Tenured
4:
1203359K->755802K
5(1398144K)
6,0.1855567 secs
7 ]1832479K->755802K
8
(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 事件开始之前,以及刚刚结束之后的内存布局,可以用下面的示意图来说明: