【阅读笔记】Java SE 规范之虚拟机垃圾回收调优指南(各种收集器 collector)

96 阅读10分钟

写作阅读笔记,实际上笔记本体是思维导图,而本人的思维导图大概只算得上是原始文档的分句版本,所以感觉这笔记……可能和照抄翻译差别不大(。)

本篇涉及章节:5 可用收集器6 并行收集器7 大多数并发收集器8 并发标记扫描收集器
5 Available Collectors6 The Parallel Collector7 The Mostly Concurrent Collectors8 Concurrent Mark Sweep(CMS)Collector

其中《7、大多数并发收集器 The Mostly Concurrent Collectors》和《8、并发标记扫描收集器 Concurrent Mark Sweep(CMS)Collector》在SE23中已被删除

本笔记所阅读的官方文档为 JDK8 和 JDK23 的同名章节《虚拟机垃圾回收调优指南》。版本不同的部分本文会有引用标记。

JavaSE 8-虚拟机垃圾回收调优指南

JavaS2 23-虚拟机垃圾回收调优指南


可用收集器 Available Collectors

Java HotSpot VM 包括三种不同类型的收集器,每种收集器具有不同的性能特征。

  • 串行收集器
  • 并行收集器
  • 大多数并发收集器

其中,串行收集器有以下几个特点:

  1. 使用单个线程来执行所有垃圾收集工作,相对高效,线程之间没有通信开销
  2. 最适合于单处理器计算机
  3. 无法利用多处理器硬件,尽管它在多处理器上对于具有较小数据集(最大约 100 MB)的应用程序非常有用。
  4. 默认情况下,在某些硬件和作系统配置上会选择串行收集器,也可以使用选项 -XX:+UseSerialGC 显式启用串行收集器。

另外两个收集器会在下面各自单独展开介绍。

并行收集器 The Parallel Collector

也称为吞吐量收集器。是类似于串行收集器的分代收集器,通过多个线程并行执行次要回收,加速垃圾回收,显著降低回收开销。适用于在多处理器或多线程硬件上运行的具有中型到大型数据集的应用程序。

使用命令行选项 -XX:+UseParallelGC 启用并行收集器。

使用时默认次要回收和主要回收并行执行,以进一步减少垃圾回收开销。

线程数配置

下面用 N 代指服务器上的线程数,对于不同的机器,Java SE 8有以下几个默认配置:

  • N < 8 :垃圾回收线程数 == N
  • N >= 8:垃圾回收线程数 == N
  • N 远大于 8 :垃圾回收线程数 ≈ 5/8
  • 在特定平台上:垃圾回收线程数 == 5/16

选定平台的原句是 On selected platforms, the fraction drops to 5/16.其后未明确写有哪些平台。

垃圾回收器线程的数量可用命令行选项进行调整。

在具有一个处理器的主机上,并行收集器的性能可能不如串行收集器,因为并行执行(例如,同步)需要开销。

当运行具有中型到大型堆的应用程序且有两个以上的处理器可用时,并行收集器的性能通常明显优于串行收集器

垃圾回收器线程数以命令行选项-XX:ParallelGCThreads= <N>来控制。

理论上,如果使用命令行选项调整堆,在保持相同性能的前提下,并行收集器所需的堆大小与串行收集器所需的堆大小相同。

但实际上,并行收集器很容易产生碎片。因为有多个线程同时参与年轻代的回收(即次要回收 minor collection),而这些线程都会预留一部分老年代空间作为对象从年轻代“晋升”到老年代时的缓冲区,这就导致老年代空间极容易碎片化。碎片化会降低内存的使用效率,导致老年代空间不足,并更频繁地触发 Full GC 。而减少碎片化的方法里,除了增加老年代空间,就是减少并行垃圾回收器的线程数。

因此实际上部署时,需要适当增加堆大小或调整老年代比例,最好是先监控 GC 行为后再调整。

并行收集器的自适应调优

默认情况下,服务器类计算机上会选择并行收集器来执行垃圾回收。并行收集器会使用一种自动优化方法,允许指定特定行为,而不是指定生成大小和其他低级优化详细信息

可指定的内容为:最大垃圾回收暂停时间、吞吐量、内存占用(堆大小)。

最大垃圾回收暂停时间 Maximum Garbage Collection Pause Time

命令行选项: -XX:MaxGCPauseMillis=\<N>

  • 默认情况下,没有最大暂停时间目标。如果指定了暂停时间目标,则会调整堆大小和与垃圾回收相关的其他参数,以尝试使垃圾回收暂停时间短于指定值。
  • 这些调整可能会导致垃圾回收器降低应用程序的整体吞吐量,并且无法始终满足所需的暂停时间目标。
吞吐量 Throughput

命令行选项: -XX:GCTimeRatio=<N>

  • 吞吐量目标的衡量标准是执行垃圾回收所花费的时间与垃圾回收之外所花费的时间(称为应用程序时间)。
    • 例如,-XX:GCTimeRatio=19 将目标设置为垃圾回收总时间的 1/20 或 5%。默认值为 99,因此垃圾回收的目标是 1% 的时间。
  • 该选项将垃圾回收时间与应用程序时间的比率设置为 1 / (1 +<N>)。
内存占用(堆大小) Footprint

命令行选项: -Xmx<N>

  • 指定最大堆内存占用。
  • 收集器有一个隐含的设定,即只要满足其他目标,就可以最小化堆的大小。
三个目标的优先级
  1. Maximum pause time goal 最长暂停时间目标
  2. Throughput goal 吞吐量目标
  3. Minimum footprint goal 最小内存占用

首先满足最大暂停时间目标。只有在满足吞吐量目标后,才会解决吞吐量目标。同样,只有在满足前两个目标后,才会考虑内存占用。

代容量(代大小)调整

收集器会在每次收集结束时更新保留下来的统计信息,如平均暂停时间等。然后进行测试以确定目标是否得到满足,并对代的规模进行必要的调整。

在保留统计信息和调整代容量方面,显式垃圾回收(例如对 System.gc() 的调用)会被忽略。

代容量的增大和缩小是通过其的固定百分比的增量完成的,并且有不同的速率。默认情况下,代以 20% 的增量增长,以 5% 的增量收缩。增长的百分比由新生代 -XX:TenuredGenerationSizeIncrement= <T> 老一代 -XX:YoungGenerationSizeIncrement= <Y> 控制。

缩小代的百分比:XX:AdaptiveSizeDecrementScaleFactor= <D>

如果增长增量为 X %,则收缩的递减量为 X / D % 。

收集器启动时可以增加代容量,文档中称其为补充百分比(supplemental percentage)。增加的这部分容量会随着垃圾回收次数的增加而逐渐衰减,不会有长期影响,目的也只是提高启动性能,因为启动时可能会有大量对象被创建和销毁。

既然启动时可以增加容量,那可以减少吗?很遗憾,没有这个操作。缩小代容量的操作是直接基于当前的内存需求和垃圾回收策略,没有额外的性能优化。前面的命令行也只有增长的。

  • 如果没有达到最大暂停时间目标,则一次只缩小一个代的容量。如果两代的暂停时间都高于目标,则首先缩小暂停时间较长的代的容量
  • 如果没有达到吞吐量目标,则两代的容量都会增加。每个 cookie 都根据其各自对总垃圾回收时间的贡献成比例增加。
    • 例如,如果新生代的垃圾回收时间占总回收时间的 25%,并且新生代的完全增量为 20%,则新生代将增加 5%。
默认堆大小

文档说,除非在命令行上指定了初始堆大小和最大堆大小,否则它们将根据计算机上的内存量进行计算。

客户端 JVM 默认/最大堆容量

这部分是 SE8 的内容,在 SE23 中没有区分客户端与服务端

在 JVM 初始化期间分配的 初始堆容量 通常最少为 8 MB,当物理内存大小为 1 GB 时,初始分配物理内存的 1/64 作为堆容量。分配给年轻代的最大空间量是总堆大小的 1/3 。

当最大物理内存大小为 192 兆字节 (MB) 时,默认最大堆容量物理内存的一半

当最大物理内存大小为 1 千兆字节 (GB) 时,默认最大堆容量物理内存的四分之一

例如,如果您的计算机具有 128 MB 的物理内存,则最大堆大小为 64 MB,大于或等于 1 GB 的物理内存将导致最大堆大小为 256 MB。

服务器 JVM 默认/最大堆容量

这部分是 SE8 的内容,在 SE23 中没有区分客户端与服务端

基本和客户端 JVM 的数值一致,但因为服务器会有更高的配置,所以会有更高的默认值。文档中分别给出了32位和64位的数值示例。

在 32 位 JVM 上,物理内存 >= 4 GB 时,默认最大堆容量最大可以达到 1 GB。 在 64 位 JVM 上,物理内存 >= 128 GB 时,默认最大堆容量最大可以达到 32GB。

命令行设置

初始堆容量:-Xms

最大堆容量:-Xmx

如果将两个数设置为相同的值,JVM 会首先使用初始堆容量,再逐渐增加,直到在堆使用和性能之间找到平衡。

其他参数和选项可能会影响这些默认值。要验证默认值需用 -XX:+PrintFlagsFinal 选项并在输出中查找 MaxHeapSize。例如,在 Linux 或 Solaris 上,您可以运行以下命令:java -XX:+PrintFlagsFinal \<GC options> -version | grep MaxHeapSize

可能出现的异常

OutOfMemoryError异常 :如果在垃圾回收 (GC) 中花费了太多时间,并行回收器将引发这个异常。如果垃圾回收所花费的总时间超过 98%,而恢复的堆不到 2%,就会引发 OutOfMemoryError。此功能旨在防止应用程序长时间运行,同时由于堆太小而几乎没有进展。

如有必要,可以通过在命令行中添加选项 -XX:-UseGCOverheadLimit 来禁用此功能。

大多数并发收集器 The Mostly Concurrent Collectors

SE23 中没有此章节

SE8 的本章节包含了后续版本中已被剔除的 CMS 收集器、G1 收集器以及部分并发开销原理,CMS 收集器下面会细说,G1收集器由于在 SE23 版本中已有更详细的优化和描述,因此笔者打算另开一篇笔记作记录。这里便只记录其中的并发开销的内容。

并发开销

  • 优点:减少垃圾收集的暂停时间,提升应用程序的响应性。

  • 缺点:占用处理器资源,降低应用程序的吞吐量。

  • 适用场景:多核系统,尤其是需要低暂停时间的应用程序。

  • 不适用场景:单核系统或处理器资源非常有限的系统。

  • 其他参考资料