企业在垃圾回收上浪费了多少钱?

我们真切地相信,企业正在垃圾回收这件事上浪费着数百万美元。我们同样也真切地认为,企业对于这样的浪费毫不知情。这篇文章的目的,就是让人们了解怎么会在垃圾回收上浪费了掉这么多钱。

什么是垃圾

一切应用程序的内存都是有限的。当出现新请求时,应用程序会创建对象来服务于该请求。请求得到处理后,所有用于服务该请求的对象就可以销毁掉了。也就是说,这些对象就变成了“垃圾”,是必须从内存中移除掉的东西,以便腾出空间来服务于新传入的请求。

垃圾回收的演变:手动 🡪 自动

三、四十年前,C 和 C++ 编程语言得到了开发人员的广泛使用。在这些编程语言中,垃圾回收的工作需要由开发人员来完成,即:应用程序开发人员需要编写代码来处理内存中没有引用的对象。如果开发人员忘记了在程序中编写该逻辑,那么应用程序将存在内存泄漏问题。内存泄漏会导致应用程序崩溃。因此,当时内存泄漏问题可谓是普遍存在。

20 世纪 90 年代中期,Java 编程语言出现了。Java 中提供了自动的垃圾回收功能,开发人员不再需要编写逻辑来处理没有引用的对象。Java 虚拟机本身会自动从内存中移除此类对象。当然,这极大地提高了生产力,开发人员非常喜欢这一功能。最重要的是,一些与内存泄漏相关的崩溃问题也得到了解决。是不是感觉目前来看还不错?但是这样的自动垃圾回收功能也有一些问题。

为了执行自动垃圾回收,JVM 必须要使应用程序停顿一下,这样才能去识别没有引用的对象并相应地进行处理。这样的停顿可能会需要几毫秒到几分钟的时间,具体取决于应用程序自身、工作负载情况与 JVM 设置。在程序停顿执行垃圾回收工作时不会对任何客户事务进行处理。正在处理过程中的客户事务会被暂停。反映到客户眼前的就是,程序的响应时间变慢。所以这就是需要权衡的地方:选择了开发人员的生产力与最大程度降低内存泄漏相关崩溃的发生几率,就会导致应用程序出现停顿时间进行自动垃圾回收工作。通过做有效的调优工作,我们可以减少停顿时间,但无法将其消除。

停顿,或许听起来好像只是对响应时间的轻微性能影响。但实际上其远不止于此。当今企业正在因为这种自动垃圾回收损失数百万美元。下面是一些有趣的事实/细节。

垃圾回收吞吐量

“GC 吞吐量”是垃圾回收调优工作中需要研究的关键指标之一。同时,该指标也机智地采用了百分比的形式。那么什么是“GC 吞吐量 %”。简而言之,该指标的意义是“程序处理客户事务所花费的时间,与处理垃圾回收工作所花费的时间的对比”。假设程序的 GC 吞吐量为 98%,这就意味着会程序将 98% 的时间用于处理客户事务,剩余 2% 的时间用于处理垃圾回收工作。

对了,98% 的 GC 吞吐量,你听起来感觉如何?鉴于人类大脑所接受的训练会自动将 98 分划归到优秀的那一类分数,所以 98% 的 GC 吞吐量听起来还不错,对吧?但事实并非如此。下面让我们来看看其背后的计算方式。

1 天有 1440 分钟(即:24 小时 x 60 分钟)。

98% 的GC吞吐量意味着应用程序会花费28.8 分钟/的时间在垃圾回收工作中。(程序在处理 GC 工作中花费了 2% 的时间。1440 分钟的 2% 为 28.8 分钟)。

这意味着什么?这意味着即使程序的 GC 吞吐量为 98%,其每天也会把 28.8 分钟(几乎 30 分钟了)花在进行垃圾回收中。在这 28.8 分钟的时间内,程序将处于停顿状态,不会为客户做任何事

一种看待这一问题的方式是:假设你买了一辆全新且昂贵的车,然后现在想去兜 2 个小时的风。如果这辆车只能跑 1 小时 50 分钟,但在路上会间歇性地停个 10 分钟,而且仍然会消耗汽油,此时你会作何感想?这就是自动垃圾回收带给开发人员的感觉。JVM 一直会间歇性停顿,就算是程序还在处理客户事务时也不例外。

浪费资金

就算是“健康”的应用程序,其 GC 吞吐量也会在 95-99% 的范围内变化。有时甚至会更低。根据程序的 GC 吞吐量百分比,我在下表中总结了中型(1k 实例/年)、大型(10k 实例/年)以及超大型(100k 实例/年)企业的浪费情况。

GC 吞吐量 %99%98%97%96%95%
1 个实例每天浪费的分钟数14.4 分钟28.8 分钟43.2 分钟57.6 分钟72 分钟
1 个实例每年浪费的小时数87.6 小时175.2 小时262.8 小时350.4 小时438 小时
中型公司(1k 实例/年)浪费的资金(美元)$50.07K$100.14K$150.21K$200.28K$250.36K
大型公司(10k 实例/年)浪费的资金(美元)$500.77K$1.00M$1.50M$2.00M$2.50M
超大型公司(100k 实例/年)浪费的资金(美元)$5.00M$10.01M$15.02M$20.02M$25.03M

以下是我在计算时所采用的假设:

  1. 中型企业将其应用程序运行在 1,000 个 EC2 实例上。大型企业将其应用程序运行在 10,000 个 EC2 实例上。超大型企业将其应用程序运行在 100,000 个 EC2 实例上。
  2. 计算过程中,我已假设这些企业正在美国西部(北加州) EC2 实例中的 t2.2x.large 32g RHEL按需实例上运行程序。此类 EC2 实例的成本是 $0.5716/小时。

从下方的图表中,我们可以看到垃圾回收给中型、大型以及超大型企业带来的资金浪费:

图:中型企业由于垃圾回收而浪费的资金

图:大型企业由于垃圾回收而浪费的资金

图:超大型企业由于垃圾回收而浪费的资金

备注 1:这里的计算基于从 95-99% 的 GC 吞吐量假设,部分应用程序的吞吐量数据要差许多。在此类情形中,浪费的资金会更多。

备注 2:计算过程中采用的是 t2.2x.large 32G RHEL 实例。部分企业会使用容量更大的机器。在此类情形中,浪费的资金会更多。

相反观点

以下是有关上述研究的相反观点:

  1. 在本次研究过程中,我所考虑的是 AWS EC2 按需实例,其实也可以在计算中根据使用专用实例的情形进行计算。按需实例和专用实例之间的差异约为 30%。所以价格浮动也在 30% 左右。不过,前述成本浪费的 70% 实际上也不是小数目了。
  2. 另一个论点是 AWS 云服务其实价格比较昂贵,计算过程中或许也应该考虑其他云服务供应商、裸金属(bare metal)服务器或无服务器架构。是的,这些都是有效的计算参数,但其只会让计算结果偏差几个百分点。不过“垃圾回收会导致资源浪费”这一点依然无法辩驳。

欢迎大家在评论区留下自己的反对意见。我将尽量回复各位的评论。

结论

在本文中,我提出了由于垃圾回收而导致浪费大量资金的案例。但不幸的是,这些成本还会在我们甚至无法察觉的地方被浪费掉。作为应用程序开发人员/管理者/主管,我们可以采取以下措施:

  1. 尝试对垃圾回收性能进行调优,这样我们的应用程序就能减少花费在这项工作上的时间。

现代程序倾向于创造诸多对象,即便是很简单的服务请求也不例外。这里有一份案例研究,其中展示了知名框架 Spring Boot 中所存在的浪费资金情况。我们可以试着编写高效的代码,这样程序就可以在为传入请求提供服务时创建不那么多的对象。如果程序所创建的对象数量少了,那么需要从内存中清除的垃圾就会变少。垃圾少了,停顿的时间也就少了。

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Blog at WordPress.com.

Up ↑

%d bloggers like this: