「译文」Java 垃圾收集参考手册(四):Serial GC

简介: 「译文」Java 垃圾收集参考手册(四):Serial GC

Serial GC(串行 GC)

Serial GC 对年轻代使用 mark-copy(标记 - 复制) 算法 , 对老年代使用 mark-sweep-compact(标记 - 清除 - 整理) 算法. 顾名思义, 两者都是单线程的垃圾收集器, 不能进行并行处理。两者都会触发全线暂停(STW), 停止所有的应用线程。

因此这种 GC 算法不能充分利用多核 CPU。不管有多少 CPU 内核, JVM 在垃圾收集时都只能使用单个核心。

要启用此款收集器, 只需要指定一个 JVM 启动参数即可, 同时对年轻代和老年代生效:

java -XX:+UseSerialGC com.mypackages.MyExecutableClass
RUBY

该选项只适合几百 MB 堆内存的 JVM, 而且是单核 CPU 时比较有用。 对于服务器端来说, 因为一般是多个 CPU 内核, 并不推荐使用, 除非确实需要限制 JVM 所使用的资源。大多数服务器端应用部署在多核平台上, 选择 Serial GC 就表示人为的限制系统资源的使用。 导致的就是资源闲置, 多的 CPU 资源也不能用来降低延迟, 也不能用来增加吞吐量。

下面让我们看看 Serial GC 的垃圾收集日志, 并从中提取什么有用的信息。为了打开 GC 日志记录, 我们使用下面的 JVM 启动参数:

-XX:+PrintGCDetails -XX:+PrintGCDateStamps
-XX:+PrintGCTimeStamps
RUBY

产生的 GC 日志输出类似这样(为了排版, 已手工折行):

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]
MATHEMATICA

此 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

  1. 2015-05-26T14:45:37.987-0200 – GC 事件开始的时间. 其中 -0200 表示西二时区, 而中国所在的东 8 区为 +0800
  2. 151.126 – GC 事件开始时, 相对于 JVM 启动时的间隔时间, 单位是秒。
  3. GC – 用来区分 Minor GC 还是 Full GC 的标志。GC表明这是一次 小型 GC(Minor GC)
  4. Allocation Failure – 触发 GC 的原因。本次 GC 事件, 是由于年轻代中没有空间来存放新的数据结构引起的。
  5. DefNew – 垃圾收集器的名称。这个神秘的名字表示的是在年轻代中使用的: 单线程, 标记 - 复制(mark-copy), 全线暂停(STW) 垃圾收集器。
  6. 629119K->69888K – 在垃圾收集之前和之后年轻代的使用量。
  7. (629120K) – 年轻代总的空间大小。
  8. 1619346K->1273247K – 在垃圾收集之前和之后整个堆内存的使用情况。
  9. (2027264K) – 可用堆的总空间大小。
  10. 0.0585007 secs – GC 事件持续的时间, 以秒为单位。
  11. [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 开始之前, 以及刚刚结束之后, 这两个时间点内存使用情况的快照:

04_01_serial-gc-in-young-generation.png

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

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

和 Minor GC 相比, 最明显的区别是 —— 在此次 GC 事件中, 除了年轻代, 还清理了老年代和 Metaspace. 在 GC 事件开始之前, 以及刚刚结束之后的内存布局, 可以用下面的示意图来说明:

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
5天前
|
Java Linux 开发者
软件体系结构 - Java垃圾收集器
【4月更文挑战第22天】软件体系结构 - Java垃圾收集器
18 4
|
22天前
|
监控 算法 Java
Java GC调优详解
Java GC调优详解
32 0
|
2月前
|
Java
andeoid 开发:Error:java.lang.OutOfMemoryError: GC overhead limit exceeded
andeoid 开发:Error:java.lang.OutOfMemoryError: GC overhead limit exceeded
12 0
|
3月前
|
Java 开发者 iOS开发
8 种 Java- 内存溢出之二 -GC overhead limit exceeded
8 种 Java- 内存溢出之二 -GC overhead limit exceeded
|
3月前
|
算法 安全 Java
「译文」Java 垃圾收集参考手册(三):GC 算法基础篇
「译文」Java 垃圾收集参考手册(三):GC 算法基础篇
|
5天前
|
数据采集 存储 Java
高德地图爬虫实践:Java多线程并发处理策略
高德地图爬虫实践:Java多线程并发处理策略
|
3天前
|
设计模式 安全 Java
【JAVA】Java 中什么叫单例设计模式?请用 Java 写出线程安全的单例模式
【JAVA】Java 中什么叫单例设计模式?请用 Java 写出线程安全的单例模式
|
1天前
|
消息中间件 监控 安全
【JAVAEE学习】探究Java中多线程的使用和重点及考点
【JAVAEE学习】探究Java中多线程的使用和重点及考点
|
1天前
|
安全 Java 开发者
构建高效微服务架构:后端开发的新范式Java中的多线程并发编程实践
【4月更文挑战第29天】在数字化转型的浪潮中,微服务架构已成为软件开发的一大趋势。它通过解耦复杂系统、提升可伸缩性和促进敏捷开发来满足现代企业不断变化的业务需求。本文将深入探讨微服务的核心概念、设计原则以及如何利用最新的后端技术栈构建和部署高效的微服务架构。我们将分析微服务带来的挑战,包括服务治理、数据一致性和网络延迟问题,并讨论相应的解决方案。通过实际案例分析和最佳实践的分享,旨在为后端开发者提供一套实施微服务的全面指导。 【4月更文挑战第29天】在现代软件开发中,多线程技术是提高程序性能和响应能力的重要手段。本文通过介绍Java语言的多线程机制,探讨了如何有效地实现线程同步和通信,以及如
|
3天前
|
安全 Java
【JAVA】线程的run()和start()有什么区别?
【JAVA】线程的run()和start()有什么区别?