相信您对 彼得原理 并不陌生。总的来说,该原则是观察到晋升可以而且将会导致被晋升的人不再有资格胜任工作的情况。
对于 JVM,存在类似的问题。过快地提升对象会对性能产生重大影响。在这篇文章中,我们打开了提升率的概念,演示了如何衡量它并解释了这个概念的实用价值。
这是我们上周解释 分配率 概念的帖子的后续。
Promotion rate 以单位时间从Young generation传播到Old generation的数据量来衡量 。它通常以 MB/sec 来衡量,类似于分配率。类似于我们关于分配率的帖子,让我们再次深入挖掘,看看提升率是如何计算的,以及为什么你应该关心分配率。
衡量晋升率
让我们从晋升率的衡量开始。为此,让我们通过为 JVM 指定 -XX:+PrintGCDetails -XX:+PrintGCTimeStamps 标志来打开 GC 日志记录。 JVM 现在开始记录 GC 暂停,如以下代码片段所示:
0.291: [GC (Allocation Failure) [PSYoungGen: 33280K->5088K(38400K)] 33280K->24360K(125952K), 0.0365286 secs] [Times: user=0.11 sys=0.02, real=0.04 secs]
0.446: [GC (Allocation Failure) [PSYoungGen: 38368K->5120K(71680K)] 57640K->46240K(159232K), 0.0456796 secs] [Times: user=0.15 sys=0.02, real=0.04 secs]
0.829: [GC (Allocation Failure) [PSYoungGen: 71680K->5120K(71680K)] 112800K->81912K(159232K), 0.0861795 secs] [Times: user=0.23 sys=0.03, real=0.09 secs]
从上面我们可以提取收集事件前后的年轻代和总堆的大小。知道年轻一代的消耗和总堆,很容易计算老一代的消耗,只是两者之间的增量。将GC日志中的信息表示为:
事件 | 时间 | 年轻减少 | 总减少 | 晋升 | 晋升率 |
---|---|---|---|---|---|
第一次GC | 291毫秒 | 28,192K | 8,920K | 19,272K | 66.2 MB/秒 |
第二次GC | 446毫秒 | 33,248K | 11,400K | 21,848K | 140.95 MB/秒 |
第三次GC | 829毫秒 | 66,560K | 30,888K | 35,672K | 93.14 MB/秒 |
全部的 | 829毫秒 | 76,792K | 92.63 MB/秒 |
将允许我们提取测量期间的促销率。我们可以看到平均提升速率为 92 MB/秒,有一段时间达到 140.95 MB/秒的峰值。
分析影响
现在,了解了提升率的定义并了解了如何衡量它,让我们来看看这些信息的实用价值。
同样,与分配率类似,提升率的主要影响是 GC 暂停频率的变化。但与影响 Minor GC 事件频率的分配率相反,提升率影响 Major GC 事件的频率。让我解释一下——你提升到老年代的东西越多,你填充它的速度就越快。更快地填充 Old gen 意味着 GC 事件清理 Old generation 的频率将会增加。
从实际的角度来看,面对高晋升率可能会出现一种称为 过早晋升 的问题症状。为了解释这个问题,让我们回顾一下为什么 JVM 堆首先被分成不同的内存池。这样做的原因是基于以下观察:
- 大多数对象很快就会变得闲置
- 那些通常不能存活很长时间的
这些观察结果汇集在 弱代际假设 中。基于这个假设,VM 内部的内存被分为所谓的年轻一代和老(或终身)一代。拥有这样独立且可单独清理的区域允许 GC 应用不同的算法来清理这些区域,从而提高 GC 的性能。
因此 ,当预期寿命较短的对象未被收集到 Young generation 并被提升到 Old generation 时,就会发生过早提升 。清理此类对象成为 Major GC 的工作,它不是为频繁运行而设计的,并且会导致更长的 GC 暂停,从而显着影响应用程序的吞吐量。
表明应用程序遭受过早提升的症状是 提升率接近分配率 。在我们的案例中,我们肯定面临这样的问题,因为我们的分配速率测量为 161 MB/秒,提升速率为 92 MB/秒。该问题的解决方案可能与通过更改 -XX:NewSize 、 -XX:MaxNewSize 和 -XX:SurvivorRatio 参数来增加年轻代的大小一样简单。
在许多情况下,这仍然会导致 Minor GC 运行过于频繁。在这种情况下,您将需要更改应用程序并降低分配率。如何实现这一点在很大程度上取决于应用程序,但为频繁创建的对象引入缓存可能是解决该问题的一种方法。
带走
从实际的角度来看,您应该关心分配和提升率,以了解 GC 能否跟上对象创建和提升到 Old Generation 的速度。这些因素会显着影响应用程序的吞吐量。使用更合适的 GC 配置或通过简单更改源代码通常可以缓解该问题。