参考文档

  1. Java Virtual Machine Technology (JDK8官方)
  2. Java Platform, Standard Edition HotSpot 虚拟机垃圾收集调优指南 (JDK8官方)
  3. Java平台标准版工具参考(unix) (JDK8官方)
  4. 阿里巴巴Dragonwell8用户指南 (Dragonwell官方)
  5. Java性能优化之JVM GC(垃圾回收机制)(大鹅coding)

目录

  • 参考文档
  • 目录
  • 前言
  • 省流
    • 默认收集器
    • 默认堆大小
    • 调优策略
    • 收集器选用原则
  • 基本定义
    • 调优目标
    • 收集器分类
    • 内存中代的排列
      • 默认排列
      • 并行收集器的排列
      • G1收集器的排列
    • 回收算法
      • 标记-清除 算法
      • 标记-复制 算法
      • 标记-整理 算法
  • 串行收集器
  • 并行收集器
    • 目标优先级
    • GC步骤
  • 并发标记扫描(CMS)收集器
  • 垃圾优先(G1)垃圾收集器
  • JVM重要调优参数
    • 通用参数
    • 串行收集器
    • 并行收集器
    • 大多数并发收集器
      • CMS
    • G1

前言

本文尽可能对GC的情况进行简要表述,目标是用最少的描述来完整阐明JDK8 Java Hotspot VM目前支持的各种GC方式及其调优方法。

为精简行文内容,正文中不包含GC观测手段的介绍,读者可以参考 Java Platform, Standard Edition HotSpot 虚拟机垃圾收集调优指南 部分获得更多相关信息。

省流默认收集器

收集器的默认配置通常遵循如下原则:

  1. 两个以上CPU 且 2GB以上的物理内存时,默认使用并行收集器
  2. 否则默认使用串行收集器

下面提供一个简单的表格:

平台操作系统默认收集器类型
i586Linux并行收集器
i586Windows串行收集器
SPARC (64-bit)Solaris并行收集器
AMD (64-bit)Linux并行收集器
AMD (64-bit)Windows并行收集器

默认堆大小

参数客户端服务端
-Xmx 最大堆大小物理内存小于等于192M时,为物理内存的一半;
否则为物理内存的1/4,但不超过256M
物理内存小于等于192M时,为物理内存的一半;
否则为物理内存的1/4,32位JVM不超过1G,64位JVM不超过32GB
-Xms 初始堆大小物理内存的1/64,但不低于8M,不超过256M物理内存的1/64,但不低于8M,不超过256M
-Xmn 新生代空间总堆大小的1/3总堆大小的1/3

调优策略

  1. 尽量不要指定最大堆值(除非你知道最优质值),这样的话堆会自行增长/减小到能满足吞吐量目标的大小。
  2. 如果最大堆值无法满足你需要的吞吐量目标,则可以考虑将最大堆值大小设置为接近总物理内存的值,但需要保证它不会导致使用到swap区域(如果仍然无法满足吞吐量目标那就要考虑增加物理内存了)。
  3. 若配置了最大停顿时间目标,则可能会导致吞吐量目标无法达到,所以要注意选择一个折衷值。
  4. 实现吞吐量目标需要更大的堆,实现最大暂停时间目标和最小占用空间目标需要更小的堆。

收集器选用原则

  1. 应用程序数据集大小
    1. 小于等于100M:串行收集器
    2. 其他情况:并行收集器 或 CMS 或 G1

基本定义调优目标

所有的GC调优主要围绕如下四个目标进行

  • 停顿时间 暂停时间是垃圾收集器停止应用程序并回收不再使用的空间的持续时间。
  • 吞吐量 可以理解为非垃圾收集的时间的占比。吞吐量目标是根据收集垃圾所花费的时间和在垃圾收集之外所花费的时间(称为 应用程序时间 )来衡量的。目标由命令行选项 -XX:GCTimeRatio= 指定。垃圾收集时间与应用程序时间的比率为 1 / (1 + )。 例如,-XX:GCTimeRatio=19 将目标设置为垃圾收集总时间的 1/20 或 5%。
  • Footprint占用空间 可以理解为堆的占用空间。
  • Promptness敏捷 对象死亡和内存变为可用之间的时间。

收集器分类

  • 串行收集器 使用单个线程来执行所有垃圾收集工作,这使得它相对高效,因为线程之间没有通信开销。 它最适合单处理器机器,因为它不能利用多处理器硬件,尽管它在多处理器上对于具有小数据集(最多大约 100 MB)的应用程序很有用。 在某些硬件和操作系统配置中默认选择串行收集器,或者可以使用选项 -XX:+UseSerialGC 显式启用。
  • 并行收集器(也称为 吞吐量收集器 并行执行次要收集,这可以显着减少垃圾收集开销。 它适用于在多处理器或多线程硬件上运行的具有中型到大型数据集的应用程序。 并行收集器在某些硬件和操作系统配置上默认选择,或者可以使用选项 -XX:+UseParallelGC 显式启用。
    • 并行压缩是一种使并行收集器能够并行执行主要收集的功能。 如果没有并行压缩,主要的收集是使用单个线程执行的,这会显着限制可伸缩性。 如果指定了选项-XX:+UseParallelGC,则默认启用并行压缩。 关闭它的选项是-XX:-UseParallelOldGC
  • 大多数并发收集器 并发执行其大部分工作(例如,当应用程序仍在运行时)以保持垃圾收集暂停时间短。 它专为具有中型到大型数据集的应用程序而设计,在这些应用程序中响应时间比总吞吐量更重要,因为用于最小化暂停的技术会降低应用程序性能。 Java HotSpot VM 提供了两个主要并发的收集器之间的选择。使用选项-XX:+UseConcMarkSweepGC 启用 CMS 收集器或使用选项-XX:+UseG1GC 启用 G1 收集器。
    • 并发标记扫描收集器(Concurrent Mark Sweep Collector, CMS) 此收集器适用于那些喜欢较短垃圾收集暂停时间并且能够与垃圾收集共享处理器资源的应用程序。
    • 垃圾优先垃圾收集器(Garbage-First Garbage Collector, G1) 这个服务器风格的收集器适用于具有大内存的多处理器机器。它在实现高吞吐量的同时,高概率地满足垃圾收集暂停时间目标。

内存中代的排列默认排列

显示默认的世代安排(用于除并行收集器和 G1 之外的所有收集器):

  1. Eden: 绝大多数对象最初在伊甸园中分配。
  2. Survivor x 2: 一个幸存者空间在任何时候都是空的,作为伊甸园中任何活物体的归宿; 另一个幸存者空间是下一次复制收集期间的目的地。 对象以这种方式在幸存者空间之间复制,直到它们足够老可以被永久使用(复制到永久代)。
  3. Virtual: 在虚拟机初始化时,为堆保留整个空间。 保留空间的大小可以使用 -Xmx 选项指定。 如果 -Xms 参数的值小于 -Xmx 参数的值,那么并非所有保留的空间都会立即提交给虚拟机。 未提交的空间在此图中标记为“虚拟”。 堆的不同部分(tenured generation 和 young generation)可以根据需要增长到虚拟空间的极限。
  4. Tenured: 永久代空间。该空间用满时会触发 Full GC。

并行收集器的排列

各个空间的使用方式与 默认排列 中的一样。

G1收集器的排列

  1. 把堆区划分成很多个大小相同的区域(Region),新、老年代也不再固定在某个区域,每一个Region都可以根据运行情况的需要,扮演Eden、Survivor、老年代区域、或者Humongous区域。
  2. 大对象会被存储到Humongous区域,G1大多数情况下会把这个区域当作老年代来看待。如果对象占用空间超过Region的容量,就会存放到N个连续的 Humongous Region 中。

回收算法

仅图示,各算法的描述可以参考 《Java性能优化之JVM GC(垃圾回收机制)- 大鹅coding》

标记-清除 算法

标记-复制 算法

注意,从Survivor如果已满,则也会使用“标记-复制”算法将对象回收到老年代区域。

标记-整理 算法

通常用于老年代的回收。

串行收集器

步骤大致如下:

  1. 各个线程运行到安全点,暂停应用程序
  2. 使用“标记-复制”算法,并清理Eden空间和Survivor空间
  3. 使用“标记-整理”算法,并清理老年代空间
  4. 如果没有满足MinHeapFreeRatio和MaxHeapFreeRatio目标,则扩展对应空间
  5. 恢复应用程序执行

并行收集器目标优先级

这些目标按以下顺序处理:

  1. 最大停顿时间目标
  2. 吞吐量目标
  3. 最小占用空间目标

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

GC步骤

步骤与串行收集器基本一直,主要区别为并行收集器的垃圾收集过程使用多线程处理。

并发标记扫描(CMS)收集器

目标优先级 与并行收集器相同。

GC步骤(仅老年代)

  1. 年轻代收集与老年代的收集是相互独立的。
  2. 老年代收集的步骤如下(“标记-清除”算法):
    1. 各个线程运行到安全点,暂停应用程序(Stop Tow world)
    2. 进入初始标记阶段,标记所有与根节点直接关联的对象(可多线程)
    3. 恢复应用程序,进入并发标记阶段
      1. (一个处理器)跟踪和标记新增对象
      2. (一个或多个处理器)遍历2中对象的可访问对象图
    4. 暂停应用(Stop Tow world),返回不可达的对象清单
    5. 恢复应用,(一个处理器)清除无法访问对象(“标记-清除”算法),重新计算和调整堆大小。
    6. 如果出现“并发模式故障”,或老年代空间不足的情况,则会触发STW和使用”并发-整理“算法的Full GC。

i-cms 增量模式
在只有一或两个处理器的服务器上,由于在并发标记阶段始终需要有处理器来执行标记过程,因此可能会造成过大的应用中断。开启增量模式可以使用“占空比”来使CMS收集器自愿放弃部分工作量,从而减少垃圾收集所占用的应用程序CPU时间片。

垃圾优先(G1)垃圾收集器

目标优先级 与并行收集器相同。

GC步骤

  1. 各个线程运行到安全点,暂停应用程序(Stop Tow world)
  2. 进入“初始标记阶段”,标记根节点能够直接关联的对象及 Survivor Region 。
  3. 恢复应用程序
    1. 跟踪和标记新增对象(可多线程)
    2. 查找2中对象的可达对象图(可多线程)
    3. 遍历与2中Survivor Region关联的卡表信息,找到被老年代所引用的对象(可多线程)
  4. 暂停应用程序(Stop Tow world)
    1. 根据以往的暂停情况预估此次要回收的区域数量(单线程)
    2. 使用“标记-复制”算法清理指定区域的空间(可多线程)
    3. 如果空间不足,则触发Full GC(“标记-整理“算法)
  5. 恢复应用程序

注意,G1收集器的排列 可以参考前文

JVM重要调优参数

本节只列出部分相对重要的参数,全部可以参数描述可以参考 Java平台标准版工具参考(unix)。

通用参数

  • -XX:+DisableExplicitGC
    启用禁用处理对 System.gc() 调用的选项。 默认情况下禁用此选项,这意味着处理对 System.gc() 的调用。 如果禁用对 System.gc() 调用的处理,JVM 仍会在必要时执行 GC。

  • -XX:InitialHeapSize=size
    设置内存分配池的初始大小(以字节为单位)。 此值必须为 0 或 1024 的倍数且大于 1 MB。 附加字母“k”或“K”表示千字节,“m”或“M”表示兆字节,“g”或“G”表示千兆字节。 默认值是在运行时根据系统配置选择的。

    请注意,-Xms 选项设置堆的最小堆大小和初始堆大小。 如果在命令行中的 -XX:InitialHeapSize 之后出现 -Xms ,则初始堆大小将设置为使用 -Xms 指定的值。

  • -XX:MaxGCPauseMillis=time
    设置最大 GC 暂停时间的目标(以毫秒为单位)。 这是一个软目标,JVM 将尽最大努力实现它。 默认情况下,没有最大暂停时间值。

  • -XX:MaxHeapSize=size
    设置内存分配池的最大大小(以字节为单位)。 此值必须是 1024 的倍数且大于 2 MB。 附加字母“k”或“K”表示千字节,“m”或“M”表示兆字节,“g”或“G”表示千兆字节。 默认值是在运行时根据系统配置选择的。 对于服务器部署,-XX:InitialHeapSize-XX:MaxHeapSize 通常设置为相同的值。

    -XX:MaxHeapSize 选项等同于 -Xmx

  • -XX:MaxHeapFreeRatio=percent
    设置 GC 事件后可用堆空间的最大允许百分比(0 到 100)。 如果可用堆空间扩展到该值以上,则堆将收缩。 默认情况下,此值设置为 70%。

  • -XX:MaxMetaspaceSize=size
    设置可以分配给类元数据的最大本机内存量。 默认情况下,大小不受限制。 应用程序的元数据量取决于应用程序本身、其他正在运行的应用程序以及系统上可用的内存量。

  • -XX:MaxNewSize=size
    为年轻一代(苗圃)设置堆的最大大小(以字节为单位)。 默认值是根据人体工程学设置的。

  • -XX:MaxRAMPercentage=percent
    将 JVM 在应用人体工程学试探法之前可用于 Java 堆的最大内存量设置为最大内存量的百分比,如 -XX:MaxRAM 选项中所述。 默认值为 25%。

    如果此选项和影响最大内存量的其他选项的组合结果大于压缩 oops 可寻址的内存范围,则指定此选项将禁用压缩 oops 的自动使用。 有关压缩 oops 的更多信息,请参阅-XX:UseCompressedOops

  • -XX:MaxTenuringThreshold=threshold
    设置用于自适应 GC 大小调整的最大使用期阈值。 最大值为 15。并行(吞吐量)收集器的默认值为 15,CMS 收集器的默认值为 6。

  • -XX:MetaspaceSize=size
    设置分配的类元数据空间的大小,该空间将在第一次超出时触发垃圾回收。 垃圾回收的阈值根据使用的元数据量增加或减少。 默认大小取决于平台。

  • -XX:MinHeapFreeRatio=percent
    设置 GC 事件后可用堆空间的最小允许百分比(0 到 100)。 如果可用堆空间低于此值,则将扩展堆。 默认情况下,此值设置为 40%。

  • -XX:MinRAMPercentage=percent
    将 JVM 在应用人体工程学试探法之前可用于 Java 堆的最大内存量设置为最大内存量的百分比,如针对小型堆的 -XX:MaxRAM 选项中所述。 小堆是大约 125 MB 的堆。 默认值为 50%。

  • -XX:NewRatio=ratio
    设置年轻代和老年代大小之间的比例。 默认情况下,此选项设置为 2。以下示例显示如何将年轻/年老比率设置为 1:

  • -XX:NewSize=size
    为年轻一代(苗圃)设置堆的初始大小(以字节为单位)。 附加字母“k”或“K”表示千字节,“m”或“M”表示兆字节,“g”或“G”表示千兆字节。

    堆的年轻代区域用于新对象。 GC 在这个区域比在其他区域更频繁地执行。 如果年轻代的大小太小,那么将会执行大量的次要 GC。 如果大小太高,则只会执行完整的 GC,这可能需要很长时间才能完成。 Oracle 建议您将新生代的大小保持在整个堆大小的一半到四分之一之间。

    -XX:NewSize 选项等同于 -Xmn

  • -XX:+PrintGC
    在每次 GC 时启用消息打印。 默认情况下,此选项被禁用。

  • -XX:+PrintGCDateStamps
    启用在每个 GC 上打印日期戳。 默认情况下,此选项被禁用。

  • -XX:+PrintGCDetails
    允许在每次 GC 时打印详细消息。 默认情况下,此选项被禁用。

  • -XX:+PrintGCTaskTimeStamps
    为每个单独的 GC 工作线程任务启用时间戳打印。 默认情况下,此选项被禁用。

  • -XX:+PrintGCTimeStamps
    在每个 GC 上启用时间戳打印。 默认情况下,此选项被禁用。

  • -XX:+ScavengeBeforeFullGC
    在每次完整 GC 之前启用年轻代的 GC。 默认情况下启用此选项。 Oracle 建议您_不要_ 禁用它,因为在 full GC 之前清除年轻代会减少从老年代空间到达年轻代空间的对象数量。 要在每次完整 GC 之前禁用年轻代的 GC,请指定 -XX:-ScavengeBeforeFullGC。

  • -XX:SurvivorRatio=ratio
    设置伊甸园空间大小和幸存者空间大小之间的比率。 默认情况下,此选项设置为 8。以下示例显示如何将 eden/survivor 空间比率设置为 4:
    -XX:SurvivorRatio=4

  • -XX:+UseGCOverheadLimit
    在抛出 OutOfMemoryError 异常之前,允许使用限制 JVM 在 GC 上花费的时间比例的策略。 默认情况下启用此选项,如果超过 98% 的总时间花在垃圾收集上并且只有不到 2% 的堆被回收,则并行 GC 将抛出一个 OutOfMemoryError。 当堆很小时,可以使用此功能来防止应用程序长时间运行而没有任何进展。 要禁用此选项,请指定 -XX:-UseGCOverheadLimit。

串行收集器

  • -XX:+UseSerialGC
    启用串行垃圾收集器的使用。 这通常是不需要垃圾收集的任何特殊功能的小型和简单应用程序的最佳选择。 默认情况下,这个选项是禁用的,收集器是根据机器的配置和 JVM 的类型自动选择的。

并行收集器

  • -XX:+UseParallelGC
    启用并行清除垃圾收集器(也称为吞吐量收集器)以通过利用多个处理器来提高应用程序的性能。

    默认情况下,这个选项是禁用的,收集器是根据机器的配置和 JVM 的类型自动选择的。 如果启用,则 -XX:+UseParallelOldGC 选项会自动启用,除非您明确禁用它。

  • -XX:+UseParallelOldGC
    启用对完整 GC 的并行垃圾收集器的使用。 默认情况下,此选项被禁用。 启用它会自动启用 -XX:+UseParallelGC 选项。

  • -XX:+UseParNewGC
    启用在年轻代中使用并行线程进行收集。 默认情况下,此选项被禁用。 当您设置 -XX:+UseConcMarkSweepGC 选项时,它会自动启用。 在 JDK 8 中弃用了不使用 -XX:+UseConcMarkSweepGC 选项的-XX:+UseParNewGC 选项。

  • -XX:InitialSurvivorRatio=ratio
    设置吞吐量垃圾收集器使用的初始幸存者空间比率(通过 -XX:+UseParallelGC 和/或 -XX:+UseParallelOldGC 选项启用)。 默认情况下,吞吐量垃圾收集器通过使用 -XX:+UseParallelGC 和 -XX:+UseParallelOldGC 选项启用自适应大小调整,并且根据应用程序行为调整幸存者空间的大小,从初始值开始。 如果禁用自适应大小调整(使用 -XX:-UseAdaptiveSizePolicy 选项),则应使用 -XX:SurvivorRatio 选项为应用程序的整个执行设置幸存者空间的大小。

    根据年轻代的大小(Y)和初始幸存者空间比例(R),可以使用以下公式计算幸存者空间的初始大小(S):
    S=Y/(R+2)

    等式中的 2 表示两个幸存者空间。 指定为初始幸存者空间比例的值越大,初始幸存者空间大小越小。

    默认情况下,初始survivor空间比例设置为8。如果新生代空间大小使用默认值(2MB),则survivor空间的初始大小将为0.2MB。

  • -XX:ParallelGCThreads=threads
    设置用于年轻代和老年代并行垃圾回收的线程数。 默认值取决于 JVM 可用的 CPU 数量。

  • -XX:+ParallelRefProcEnabled
    启用并行引用处理。 默认情况下,此选项被禁用。

大多数并发收集器

  • -XX:ConcGCThreads=threads
    设置用于并发 GC 的线程数。 默认值取决于 JVM 可用的 CPU 数量。

  • -XX:InitiatingHeapOccupancyPercent=percent
    设置开始并发 GC 周期的堆占用百分比(0 到 100)。 它被垃圾收集器使用,根据整个堆的占用情况触发并发 GC 周期,而不仅仅是其中一个代(例如,G1 垃圾收集器)。

CMS

  • -XX:+UseConcMarkSweepGC
    为老年代启用 CMS 垃圾收集器。 当吞吐量 (-XX:+UseParallelGC) 垃圾收集器无法满足应用程序延迟要求时,Oracle 建议您使用 CMS 垃圾收集器。 G1 垃圾收集器 (-XX:+UseG1GC) 是另一种选择。

    默认情况下,这个选项是禁用的,收集器是根据机器的配置和 JVM 的类型自动选择的。 启用此选项后,将自动设置 -XX:+UseParNewGC 选项,您不应禁用它,因为以下选项组合已在 JDK 8 中弃用:-XX:+UseConcMarkSweepGC -XX:-UseParNewGC .

  • -XX:+CMSClassUnloadingEnabled
    使用并发标记清除 (CMS) 垃圾收集器时启用类卸载。 默认情况下启用此选项。 要禁用 CMS 垃圾收集器的类卸载,请指定 -XX:-CMSClassUnloadingEnabled。

  • -XX:CMSExpAvgFactor=percent
    设置在计算并发收集统计信息的指数平均值时用于对当前样本加权的时间百分比(0 到 100)。

  • -XX:CMSInitiatingOccupancyFraction=percent
    设置开始 CMS 收集周期的老年代占用百分比(0 到 100)。 默认值设置为 -1。 任何负值(包括默认值)都意味着“-XX:CMSTriggerRatio”用于定义初始占用率的值。

  • -XX:+CMSScavengeBeforeRemark
    在 CMS 标记步骤之前启用清理尝试。 默认情况下,此选项被禁用。

  • -XX:CMSTriggerRatio=percent
    设置在 CMS 收集周期开始之前分配的由 -XX:MinHeapFreeRatio 指定的值的百分比(0 到 100)。 默认值设置为 80%。

  • -XX:+ExplicitGCInvokesConcurrent
    通过使用 System.gc() 请求启用并发 GC 调用。 默认情况下禁用此选项,只能与 -XX:+UseConcMarkSweepGC 选项一起启用。

  • -XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses
    通过使用 System.gc() 请求并在并发 GC 周期期间卸载类来启用并发 GC 调用。 默认情况下禁用此选项,只能与 -XX:+UseConcMarkSweepGC 选项一起启用。

  • -XX:+UseCMSInitiatingOccupancyOnly
    允许使用占用值作为启动 CMS 收集器的唯一标准。 默认情况下,此选项被禁用,可以使用其他标准。

G1

  • -XX:+UseG1GC
    启用垃圾优先 (G1) 垃圾收集器的使用。 它是一种服务器风格的垃圾收集器,针对具有大量 RAM 的多处理器机器。 它很有可能满足 GC 暂停时间目标,同时保持良好的吞吐量。 建议将 G1 收集器用于需要大堆(大小约为 6 GB 或更大)且 GC 延迟要求有限(稳定且可预测的暂停时间低于 0.5 秒)的应用程序。

    默认情况下,这个选项是禁用的,收集器是根据机器的配置和 JVM 的类型自动选择的。

  • -XX:G1HeapRegionSize=size
    设置使用垃圾优先 (G1) 收集器时 Java 堆被细分的区域大小。 该值可以介于 1 MB 和 32 MB 之间。 默认区域大小是根据堆大小根据人体工程学确定的。

  • -XX:+G1PrintHeapRegions
    启用有关哪些区域已分配以及哪些区域已由 G1 收集器回收的信息的打印。 默认情况下,此选项被禁用。

  • -XX:G1ReservePercent=percent
    将保留的堆百分比(0 到 50)设置为假上限,以减少 G1 收集器提升失败的可能性。 默认情况下,此选项设置为 10%。

  • -XX:+UseStringDeduplication
    启用字符串去重。 默认情况下,此选项被禁用。 要使用此选项,您必须启用垃圾优先 (G1) 垃圾收集器。 请参阅-XX:+UseG1GC选项。