这个组合的垃圾收集器使用标记-复制的方式清理年轻代,使用标记-清除-整理的方式清理老年代。正如名字所指,这些收集器都是单线程的,不能并行处理。它们也都会触发Stop-the-world
暂停,挂起应用的全部线程。
Serial GC
算法不能发挥出现代多核处理器的优势,因为它不依赖与 CPU 核心数,在垃圾收集时仅仅使用一个核心。
要为年轻代和老年代使用这种垃圾收集器,只需要在 JVM 启动脚本中加入一个参数就行:
1
java -XX:+UseSerialGC com.mypackages.MyExecutableClass
Serial GC
算法对运行在单核 CPU 且堆只有几百兆字节的 JVM 来说是合理并推荐使用的。对绝大部分部署在服务端的 JVM 来说,这是非常少见的。因为大部分服务端 JVM 都会部署在多核处理器环境下,选择Serial GC
算法,就等于人为设置系统资源利用上限。这会导致资源空闲,而如果充分利用这些资源原本是可以降低延迟或增加系统吞吐量的。
让我们来看看当使用Serial GC
算法时垃圾收集的处理日志,并看看能从中获得哪些有用的信息。为此,我们需要开启 GC 日志。
1
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps
日志输出:
1
2
2015-05-26T14:45:37.987-0200: 151.126: [GC (Allocation Failure) 151.126: [DefNew: 629119K->69888K(629120K), 0.0584157 secs] 1619346K->1273247K(2027264K), 0.0585007 secs] [Times: user=0.06 sys=0.00, real=0.06 secs]
2015-05-26T14:45:59.690-0200: 172.829: [GC (Allocation Failure) 172.829: [DefNew: 629120K->629120K(629120K), 0.0000372 secs]172.829: [Tenured: 1203359K->755802K(1398144K), 0.1855567 secs] 1832479K->755802K(2027264K), [Metaspace: 6741K->6741K(1056768K)], 0.1856954 secs] [Times: user=0.18 sys=0.00, real=0.18 secs]
这点 GC 日志片段展示了很多 JVM 内部所发生的事情。事实上,这两条日志中发生了 2 次垃圾收集事件,一个清理了年轻代,一个清理了整个堆。我们先来看看发生在年轻代的第一个事件。
下面的日志片段包含了清理年轻代的垃圾收集事件信息:
2015-05-26T14:45:37.987-0200
:151.126
: [GC
(Allocation Failure
) 151.126 : [DefNew
:629119K -> 69888K
(629120K)
, 0.0584157 secs]1619346K -> 1273247K
(2027264K)
,0.0585007 secs
][Times: user=0.06 sys=0.00, real=0.06 secs]
2015-05-26T14:45:37.987-0200
– GC 开始时间。151.126
– GC 开始时间,相对于 JVM 的启动时间的偏移,单位秒。GC
– 标志位:Minor GC或Full GC。本次为Minor GC。Allocation Failure
– 触发垃圾收集的原因。本次为年轻代空间不足以分配新对象。DefNew
– 使用的垃圾收集器名称。这里使用单线程标记-复制且Stop-the-world
垃圾收集器清理年轻代。629119K -> 69888K
– 垃圾收集前后年轻代空间使用量。(629120K)
– 年轻代空间大小。1619346K -> 1273247K
– 垃圾收集前后堆上空间使用量。(2027264K)
– 堆大小。0.0585007 secs
– 垃圾收集时长,单位秒。[Times: user=0.06 sys=0.00, real=0.06 secs]
– 分类统计的垃圾收集时长:
- user:垃圾收集中的线程占用的 CPU 总时间
- sys:系统调用和等待系统事件占用的 CPU 时间
- real:应用暂停时长。由于Serial GC是单线程的,因此 。
从上面的日志我们可以洞悉在垃圾收集时 JVM 中内存使用的变化情况。垃圾收集之前,堆空间使用量为 1,619,346K,其中年轻代使用了 629,119K,我们可以计算出老年代的使用量为 990,227K。
一个更重要的结论影藏在下一批数据之中,它表明,垃圾收集之后,年轻代使用量下降了 559,231K,但是堆的使用量仅下降了 346,099K。从这里我们可推出有 213,132K 的对象从年轻代提升到了老年代。
下图示意了垃圾收集前后的内存使用情况。
理解了Minor GC事件之后,我们来看看Full GC事件日志片段:
15-05-26T14:45:59.690-0200
:172.829
: [GC (Allocation Failure) 172.829:[DefNew: 629120K->629120K(629120K), 0.0000372 secs]
172.829 : [Tenured
:1203359K->755802K
(1398144K)
,0.1855567 secs
]1832479K->755802K
(2027264K)
,[Metaspace: 6741K->6741K(1056768K)]
[Times: user=0.18 sys=0.00, real=0.18 secs]
15-05-26T14:45:59.690-0200
– GC 开始时间。172.829
– GC 开始时间,相对于 JVM 的启动时间的偏移,单位秒。[DefNew: 629120K->629120K(629120K), 0.0000372 secs]
– 年轻代中由于对象分配失败而触发一次Minor GC,运行了和上面日志中同样的叫DefNew
的收集器,它把年轻代的使用量从 629,120K 降到 0。注意,JVM 由于 bug 而导致日志有误:日志说年轻代空间被用完了。Minor GC花费了 0.0000372 秒。Tenured
– 清理老年代的垃圾收集器名称。Tenured
这个名称说明使用了单线程标记-清除-整理且Stop-the-world
的垃圾收集器。1203359K->755802K
– 垃圾收集前后老年代的空间使用量。(1398144K)
– 老年代空间大小。0.1855567 secs
– 老年代垃圾清理时长。1832479K->755802K
– 清理年轻代和老年代前后堆上空间使用量。(2027264K)
– 堆大小。[Metaspace: 6741K->6741K(1056768K)]
– 元数据区垃圾收集信息,本次垃圾回收事件在元数据区没有建树。[Times: user=0.18 sys=0.00, real=0.18 secs]
– 分类统计的垃圾收集时长:
- user:垃圾收集中的线程占用的 CPU 总时间
- sys:系统调用和等待系统事件占用的 CPU 时间
- real:应用暂停时长。由于Serial GC是单线程的,因此 。
Full GC跟Minor GC的区别很明显 – 除了年轻代,老年代和元数据区同样被清理了。下图示意了Full GC前后内存使用情况。
原文地址:GC Algorithms: Basics。