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

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 「译文」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日志并进行多维度分析。
相关文章
|
3月前
|
监控 Java Linux
Java 性能调优:调整 GC 线程以获得最佳结果
Java 性能调优:调整 GC 线程以获得最佳结果
93 11
|
4月前
|
监控 算法 Java
深入理解Java中的垃圾回收机制在Java编程中,垃圾回收(Garbage Collection, GC)是一个核心概念,它自动管理内存,帮助开发者避免内存泄漏和溢出问题。本文将探讨Java中的垃圾回收机制,包括其基本原理、不同类型的垃圾收集器以及如何调优垃圾回收性能。通过深入浅出的方式,让读者对Java的垃圾回收有一个全面的认识。
本文详细介绍了Java中的垃圾回收机制,从基本原理到不同类型垃圾收集器的工作原理,再到实际调优策略。通过通俗易懂的语言和条理清晰的解释,帮助读者更好地理解和应用Java的垃圾回收技术,从而编写出更高效、稳定的Java应用程序。
|
3月前
|
XML Java 数据格式
Java正则表达式大全(参考)
Java正则表达式大全(参考)
|
5月前
|
缓存 监控 Java
"Java垃圾回收太耗时?阿里HBase GC优化秘籍大公开,让你的应用性能飙升90%!"
【8月更文挑战第17天】阿里巴巴在HBase实践中成功将Java垃圾回收(GC)时间降低90%。通过选用G1垃圾回收器、精细调整JVM参数(如设置堆大小、目标停顿时间等)、优化代码减少内存分配(如使用对象池和缓存),并利用监控工具分析GC行为,有效缓解了高并发大数据场景下的性能瓶颈,极大提升了系统运行效率。
127 4
|
4月前
|
监控 算法 Java
深入理解Java中的垃圾回收机制(GC)
本文将探讨Java的自动内存管理核心——垃圾回收机制。通过详细解析标记-清除算法、复制算法和标记-整理算法等常用垃圾回收算法,以及CMS、G1等常见垃圾回收器,帮助读者更好地理解Java应用的性能优化和内存管理。同时,探讨分代收集、分区收集等策略在实际项目中的应用。结语部分总结了垃圾回收机制在Java开发中的重要性,并展望了未来可能的发展。
101 0
|
6月前
|
Java
idea启动java服务报错OutOfMemoryError: GC overhead limit exceeded解决方法
idea启动java服务报错OutOfMemoryError: GC overhead limit exceeded解决方法
1120 0
|
3天前
|
监控 Java
java异步判断线程池所有任务是否执行完
通过上述步骤,您可以在Java中实现异步判断线程池所有任务是否执行完毕。这种方法使用了 `CompletionService`来监控任务的完成情况,并通过一个独立线程异步检查所有任务的执行状态。这种设计不仅简洁高效,还能确保在大量任务处理时程序的稳定性和可维护性。希望本文能为您的开发工作提供实用的指导和帮助。
40 17
|
14天前
|
Java
Java—多线程实现生产消费者
本文介绍了多线程实现生产消费者模式的三个版本。Version1包含四个类:`Producer`(生产者)、`Consumer`(消费者)、`Resource`(公共资源)和`TestMain`(测试类)。通过`synchronized`和`wait/notify`机制控制线程同步,但存在多个生产者或消费者时可能出现多次生产和消费的问题。 Version2将`if`改为`while`,解决了多次生产和消费的问题,但仍可能因`notify()`随机唤醒线程而导致死锁。因此,引入了`notifyAll()`来唤醒所有等待线程,但这会带来性能问题。
Java—多线程实现生产消费者
|
16天前
|
安全 Java Kotlin
Java多线程——synchronized、volatile 保障可见性
Java多线程中,`synchronized` 和 `volatile` 关键字用于保障可见性。`synchronized` 保证原子性、可见性和有序性,通过锁机制确保线程安全;`volatile` 仅保证可见性和有序性,不保证原子性。代码示例展示了如何使用 `synchronized` 和 `volatile` 解决主线程无法感知子线程修改共享变量的问题。总结:`volatile` 确保不同线程对共享变量操作的可见性,使一个线程修改后,其他线程能立即看到最新值。
|
16天前
|
消息中间件 缓存 安全
Java多线程是什么
Java多线程简介:本文介绍了Java中常见的线程池类型,包括`newCachedThreadPool`(适用于短期异步任务)、`newFixedThreadPool`(适用于固定数量的长期任务)、`newScheduledThreadPool`(支持定时和周期性任务)以及`newSingleThreadExecutor`(保证任务顺序执行)。同时,文章还讲解了Java中的锁机制,如`synchronized`关键字、CAS操作及其实现方式,并详细描述了可重入锁`ReentrantLock`和读写锁`ReadWriteLock`的工作原理与应用场景。