[翻译]The Unspoken the Why of GC Ergonomics
Contents
你有使用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 GC
和 full GC
之间的 GC时间. 在 young GC
中, younger
对象被复制到 survivor
空间中, 而 older
对象被复制到 tenured generation
中. younger
与 older
是通过 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 GC
和 full 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中的时间更短(作为吞吐量目标).