你有使用GC工效学(即:让GC自动调整), 使用带有 -XX:+UseAdaptiveSizePolicy 选项的 UseParallelGC 收集器? 该 GC 的工效学的杰出之处在于它试图增大或缩小堆以达到指定的目标. 你可以选择的目标有: 最大暂停时间, 和/或 吞吐量. 不要太激动. 我讨论的是 UseParallelGC (即吞吐量收集器, throughput collector), 因此对于实现什么样的暂停目标有明确的限制. 当你大声说出, “我不在乎暂停时间, 给我最好的吞吐量”, 然后对自己说: “好吧, 也许10秒真的太长了”, 然后考虑暂停时间目标. 默认情况下, 没有暂停时间目标, 吞吐量目标很高(98% 的应用程序工作时间, 和 2% 的GC工作时间). 你可以在我的第一篇blog中获取更多详细信息 GC ergonomics

G1 GC 有自己的 GC 工效学版本, 但这里仅讨论 UseParallelGC 版本.

如果你使用该选项, 并且想知道它(GC 工效学)在想什么, 可以试下添加以下选项: -XX:AdaptiveSizePolicyOutputInterval=1 这将打印出每个第 i 个GC的信息(i>1) , 关于 GC 工效学试图做什么. 例如:

	UseAdaptiveSizePolicy actions to meet  *** throughput goal ***
                       GC overhead (%)
    Young generation:       16.10         (attempted to grow)
    Tenured generation:      4.67         (attempted to grow)
    Tenuring threshold:    (attempted to decrease to balance GC costs) = 1

GC 试图满足(按顺序):

  • 暂停时间目标
  • 吞吐量目标
  • 最小占用内存目标

上面第一行输出告诉我们它正尝试满足吞吐量目标:

UseAdaptiveSizePolicy actions to meet  *** throughput goal ***

这是执行默认暂停时间目标(例如, 没有暂停时间目标), 因此, 它意图达到 98% 的吞吐量. 下面这些行:

    Young generation:       16.10         (attempted to grow)
    Tenured generation:      4.67         (attempted to grow)

显示出我们当前正花 16% 的时间进行 young GC, 以及约 5% 的时候进行 full GC. 这百分比是衰减的加权平均值(早些的时候对平均贡献小). 源码是 OpenJDK 的一部分, 所以你可以看看, 如果你想要确切的定义的话. GC 工效学正在尝试通过增加堆来增加吞吐量(所以说是 attempted to grow).

最后一行:

Tenuring threshold:    (attempted to decrease to balance GC costs) = 1

表明工效学试图通过降低 tenuring threshold (晋升阈值) 来平衡 young GCfull GC 之间的 GC时间. 在 young GC 中, younger 对象被复制到 survivor 空间中, 而 older 对象被复制到 tenured generation 中. youngerolder 是通过 tenuring threshold 来定义的. 如果 tenured threshold 为4, 那么一个存活不到4次 young GC 的对象(并且通过复制到 young generation部分被称为 survivor 空间), 表示它是 younger. 并且再次复制到 survivor 空间. 如果它已经存活了4或更多的 young GC 它就会变成 older, 并被复制到 old generation. 较低的 tenured threshold 会更热切地将对象移动到 tenured generation, 相反较高的 tenured threshold 可以使 survivor 之间的对象复制时间更长. 使用 tenured threshold 的值会随着 UseParallelGC 动态变化. 这与我们其他拥有静态 tenuring threshold 的GC不同. GC 工效学试图通过改变 tenured threshold 来平衡 young GCfull GC 所完成的工作量. 想要在 young GC 中完成更多的工作? 通过增加 tenured threshold 的值, 将对象保留在 survivor 空间中的时间会更长.

以下是一个GC工效学试图满足暂停时间目标的一个例子:

    UseAdaptiveSizePolicy actions to meet  *** pause time goal ***
                       GC overhead (%)
    Young generation:       20.74         (no change)
    Tenured generation:     31.70         (attempted to shrink)

暂停时间目标设置为 50 ms, 并且最后一次 GC为:

0.415: [Full GC (Ergonomics) [PSYoungGen: 2048K->0K(26624K)] [ParOldGen: 26095K->9711K(28992K)] 28143K->9711K(55616K), [Metaspace: 1719K->1719K(2473K/6528K)], 0.0758940 secs] [Times: user=0.28 sys=0.00, real=0.08 secs]

full GC 花费约 76 ms, 因此, GC 工效学想缩小(shrink) tenured generation 来减少暂停时间. 前一次的 young GC 为:

0.346: [GC (Allocation Failure) [PSYoungGen: 26624K->2048K(26624K)] 40547K->22223K(56768K), 0.0136501 secs] [Times: user=0.06 sys=0.00, real=0.02 secs]

暂停时间约为 14 ms, 因此无需更改.

如果试图满足暂停时间目标, 那么 generation (代)通常会收缩. 在暂停时间目标内, 观察GC overhead(开销)数字, 你通常会看到暂停时间目标的成本(即吞吐量下降). 如果暂停目标太低, 你将无法实现暂停时间目标, 并且你将花费所有时间进行 GC.

GC 工效学意味着很简单, 因为它意味着被任何人使用. 这并不意味着神性, 所以这个输出被添加了. 如果你不喜欢 GC 工效学所做的工作, 可以使用 -XX:-UseAdaptiveSizePolicy 将其关闭, 但应预先警告你, 必须明确地管理 generation 的大小. 如果 UseAdaptiveSizePolicy 关闭, 堆不会增长. 在开始执行时堆(和 generations) 的大小始终是堆的大小. 我不喜欢这样, 并曾经试图修复它(人 OpenJDK 贡献者的帮助下), 但不幸的是, 它从来没有出现它. 尽管如此, 我仍抱希望.

这只是一个侧面说明. 在默认的吞吐量目标为 98% 的情况下, 堆通常会增长到最大值并停留在那里. 如果占用内存也很重要, 肯定会降低吞吐量目标. 以 -XX:GCTimeRatio=4 开始, 以获得更适合的吞吐量目标(在GC中花费的时间的 20%). 较高的值意味着GC中的时间更短(作为吞吐量目标).