什么是分配率?

一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / Java 学习路线 / 一对一提问 / 学习打卡/ 赠书活动

目前,正在 星球 内带小伙伴们做第一个项目:全栈前后端分离博客项目,采用技术栈 Spring Boot + Mybatis Plus + Vue 3.x + Vite 4手把手,前端 + 后端全栈开发,从 0 到 1 讲解每个功能点开发步骤,1v1 答疑,陪伴式直到项目上线,目前已更新了 204 小节,累计 32w+ 字,讲解图:1416 张,还在持续爆肝中,后续还会上新更多项目,目标是将 Java 领域典型的项目都整上,如秒杀系统、在线商城、IM 即时通讯、权限管理等等,已有 870+ 小伙伴加入,欢迎点击围观

诸如“不可持续的分配率”和“您需要保持较低的分配率”之类的短语似乎只属于 Java Champions 的词汇。复杂、可怕并被魔法光环包围。

正如经常发生的那样,当您更仔细地查看这些概念时,魔法会随着一股烟雾消失。这篇文章将尝试消除上述术语的魔力。

什么是分配率,我为什么要关心?

分配率以每个时间单位分配的内存量来衡量。通常以 MB/秒表示,但如果您愿意,也可以使用 PB/每光年。所以这就是全部——没有魔法,只是在一段时间内测量的您在 Java 代码中分配的内存量。

仅仅知道这个事实并没有太大好处。如果你能忍受我,我会带你了解这个概念的实际应用。

面对高分配率可能意味着应用程序的性能出现问题。从实际的角度来看, 垃圾收集 成为瓶颈,这一影响就浮出水面。从硬件的角度来看,即使是商品硬件也可以支持每个核心数 GB/秒的分配,因此如果您的速率没有开始超过 1 GB/秒/核心,您可以放心,您的硬件实际上不会成为瓶颈。

因此,当关注 GC 时,我们可以从一个在现实世界中也适用的类比开始——如果你创建了很多东西,之后你往往会面临很多清理工作。知道 JVM 是用垃圾收集机制构建的,需要研究分配率如何改变 GC 暂停的频率或持续时间。

衡量分配率

让我们从分配率的测量开始。为此,让我们通过为 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 日志中, 我们可以计算分配率,即上一次收集完成后和下一次收集开始前的年轻代大小之间的差异。 使用上面的示例,我们可以提取以下信息:

  • 在 JVM 启动后 291 毫秒 ,创建了 33,280K 个对象。 第一次小型 GC 事件清理了年轻代,之后年轻代中还剩下 5,088K 的对象。
  • 在启动后 446 毫秒,Young gen 占用率增长到 38,368K 触发下一次 GC, 这成功地将 Young gen 占用率降低到 5,120K
  • 在启动后 829 毫秒,年轻代大小为 71,680K ,GC 再次将其减少到 5,120K

然后可以在下表中表示此数据,计算分配率作为 Young 占用率的增量:

EventTime young在承诺期间递增后 1ST GC 291MS 33,280KB 5,088KB 33,280KB 33,280KB 114MB/SEC 2ND GC 446MS 38,368KB 38,368KB 5,120KB 5,120KB 33,280KB 33,280KB 215MB/ SEC 3RD/SEC 3RD/SEC 82911 and 1 11 and 111 and 111 and 111 and 5,669m n669m n669m n669m n669m n669m n669m an 161MB/秒

有了这些信息,我们就可以说这个特定的软件在测量期间的分配率为 161 MB/秒。

分析影响

现在,有了这些信息,我们就可以了解分配率的变化如何通过增加或减少 GC 暂停的频率来影响应用程序吞吐量。首先,您应该注意到,只有清理年轻代的 Minor GC 暂停受到影响。 GC 暂停清理老年代的频率和持续时间不受 分配率的 直接影响,而是受 提升率的影响 ,我们将在下一篇文章中介绍这个术语。

知道我们可以只关注 Minor GC 暂停,接下来我们应该研究年轻一代中的不同内存池。由于分配发生在 Eden 中,我们可以立即研究 Eden 的大小如何影响分配率。因此我们可以假设增加 Eden 的大小将减少次要 GC 暂停的频率,从而使应用程序能够维持更快的分配率。

事实上,当使用 -XX:NewSize -XX:MaxNewSize 和 -XX:SurvivorRatio 参数运行具有不同 Eden 大小的相同示例时,我们可以看到分配率的两倍差异

  • 使用 100M 的 Eden 运行上面的示例,将分配速率降低到 100MB/秒以下
  • 将 Eden 大小增加到 1GB,将分配速率增加到略低于 200MB/秒。

如果您仍然想知道为什么这是真的—— 如果您停止应用程序线程以进行 GC 的频率较低,您可以做更多有用的工作。更多有用的工作也恰好创建更多对象,从而支持增加的分配率

现在,在您得出“伊甸园越大越好”的结论之前,您应该注意到分配率可能并且可能不直接与应用程序的实际吞吐量相关。这是一种技术测量,有助于吞吐量。分配率可以并且将会影响您的次要 GC 暂停停止应用程序线程的频率,但要查看整体影响,您还需要考虑主要 GC 暂停并衡量吞吐量,而不是以 MB/秒为单位,而是在您的应用程序的业务运营中提供。