1

Spring Boot 应用程序浪费的内存

 8 months ago
source link: https://www.diguage.com/post/memory-wasted-by-spring-boot-application/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Spring Boot 应用程序浪费的内存

2024-01-08
Spring Boot 应用程序浪费的内存

当今世界被广泛浪费的资源之一是:内存。由于编程效率低下,内存浪费量惊人(有时 "令人震惊")。我们在多个企业应用程序中都看到了这种情况。为了证明这一点,我们进行了一项小型研究。我们分析了著名的 Spring Boot Pet Clinic 应用程序,看看它浪费了多少内存。该应用程序由社区设计,旨在展示如何使用 Spring 应用程序框架构建简单但功能强大的面向数据库的应用程序。

  • Spring Boot 2.1.4.RELEASE

  • Java SDK 1.8

  • Tomcat 8.5.20

  • MySQL 5.7.26 with MySQL Connector/J 8.0.15

我们使用流行的开源压力测试工具 Apache JMeter 进行压力测试。我们使用以下设置执行了 30 分钟的压力测试:

  • 线程数(用户)- 1000(连接到目标的用户数量)

  • 上升周期(秒) - 10。所有请求开始的时间范围。根据我们的配置,每 0.01 秒将启动 1 个新线程,即 100 个线程/秒。

  • 循环次数 - 永久。这 1000 个线程将背靠背执行测试迭代。

  • 持续时间(秒) - 1800。启动后,1000 个线程持续运行 1800 秒。

JMeter 设置
Figure 1. JMeter 设置

我们在负载测试中使用了以下场景:

  • 在系统中添加新的宠物主人。

  • 查看宠物主人的相关信息。

  • 向系统中添加一只新宠物。

  • 查看宠物相关信息。

  • 在宠物探视历史中添加探视信息。

  • 更新宠物相关信息。

  • 更新宠物主人的相关信息。

  • 通过搜索主人姓名查看主人信息。

  • 查看所有主人的信息。

如何测量内存浪费?

业界有数百种工具可以显示内存使用量。但是,我们很少遇到能测量因低效编程而浪费的内存量的工具。 HeapHero 是一款简单的工具,它可以分析堆转储,并告诉我们由于编程效率低下而浪费了多少内存。

测试运行时,我们捕获了 Spring Boot 宠物诊所应用程序的 Heap Dump。

我们将捕获的 Heap Dump 上传到 HeapHero 工具。工具生成的漂亮报告显示,由于编程效率低下,65% 的内存被浪费了。是的,这是一个简单的应用程序,本应采用所有最佳实践,但在一个备受赞誉的框架上却浪费了 65% 的内存。

HeapHero 生成的图表显示,Spring Boot 宠物诊所应用程序浪费了 65% 的内存
Figure 2. HeapHero 生成的图表显示,Spring Boot 宠物诊所应用程序浪费了 65% 的内存

分析内存浪费情况

从报告中可以看出以下几点:

  • 15.6% 的内存因字符串重复而浪费

  • 14.6% 的内存因低效的原始数组而浪费

  • 14.3% 的内存因重复的原始数组而被浪费

  • 12.1% 的内存因低效的集合而被浪费

重复字符串

在 Spring Boot 应用程序(以及大多数企业应用程序)中,造成内存浪费的首要原因是:字符串重复。报告显示了因字符串重复而浪费的内存总量、字符串的类型、创建者以及优化方法。

重复字符串
Figure 3. 重复字符串

你可以发现,15.6% 的内存是由于重复字符串造成的。请注意

  • “Goldi” 字符串已被创建 207,481 次。

  • “Visit” 字符串被创建了 132 308 次。“Visit” 是我们在测试脚本中提到的描述。

  • “Bangalore” 字符串已创建 75,374 次。“Bangalore” 是我们在测试脚本中指定的城市名称。

  • “123123123” 已被创建 37687 次。

  • “Mahesh” 字符串已被创建 37,687 次。

显然,“Goldi” 是通过测试脚本在屏幕上输入的宠物名称。“Visit” 是通过测试脚本在屏幕上输入的描述。类似的还有数值。但问题是,为什么要创建成千上万次相同的字符串对象呢?

我们都知道,字符串是不可变的(即一旦创建,就无法修改)。既然如此,为什么要创建成千上万个重复的字符串呢?

HeapHero 工具还会报告创建这些重复字符串的代码路径。

重复字符串产生的代码路径
Figure 4. 重复字符串产生的代码路径

低效的集合

Spring Boot 宠物诊所应用程序内存浪费的另一个主要原因是:集合实现效率低下。以下是 HeapHero 报告的摘录:

由于低效的集合而浪费的内存
Figure 5. 由于低效的集合而浪费的内存

你可以注意到,内存中 99% 的 LinkedHashSet 都没有任何元素。如果没有元素,为什么还要创建 LinkedHashSet 呢?当你创建一个新的 LinkedHashSet 对象时,内存中会预留 16 个元素的空间。现在,为这 16 个元素预留的所有空间都被浪费了。如果对 LinedHashset 进行懒初始化,就不会出现这个问题了。

同样,另一个观察结果是:68% 的 ArrayList 只包含 1 个元素。创建 ArrayList 对象时,内存中预留了 10 个元素的空间。这意味着 88% 的 ArrayList 中浪费了 9 个元素的空间。如果能用容量初始化 ArrayList,就可以避免这个问题。

坏的实践:使用默认构造函数初始化集合
最佳实践:使用指定容量初始化集合

内存并不便宜

有人会反驳说,内存这么便宜,我为什么要担心呢?这个问题很有道理。但在云计算时代,内存可不便宜。有 4 种主要计算资源:

应用程序可能运行在 AWS EC2 实例上的数以万计的应用程序服务器上。在上述 4 种计算资源中,EC2 实例中哪种资源会达到饱和?在继续阅读之前,请先暂停一下。想一想,哪种资源会首先饱和。

对于大多数应用程序来说,它是内存。CPU 始终保持在 30% - 60%。存储空间总是很充裕。网络很难饱和(除非应用程序正在流式传输大量视频内容)。因此,对于大多数应用程序来说,首先饱和的是内存。即使 CPU、存储和网络的利用率很低,但由于内存已经饱和,最终还是要配置越来越多的 EC2 实例。这将使计算成本增加数倍。

另一方面,由于低效的编程实践,现代应用程序无一例外地浪费了 30% - 90% 的内存。即使是没有太多业务逻辑的 Spring Boot 宠物诊所也要浪费 65% 的内存。真正的企业应用浪费的内存量级与此类似,甚至更多。因此,如果能编写内存效率高的代码,就能降低计算成本。由于内存是最先饱和的资源,如果能减少内存消耗,就能在更少的服务器实例上运行应用程序。或许可以减少 30 - 40% 的服务器。这意味着你的管理层可以减少 30 - 40% 的数据中心(或云托管服务提供商)成本,再加上维护和支持成本。这可以节省数百万/数十亿美元的成本。

除了降低计算成本,编写内存效率高的代码还能大大改善客户体验。如果能减少为处理新接收请求而创建的对象数量,响应时间就会大大缩短。由于创建的对象数量减少,用于创建和垃圾回收对象的 CPU 周期也会减少。响应时间的缩短将带来更好的客户体验。

看在D瓜哥码字的辛苦上,请友情支持一下,D瓜哥感激不尽,😜

微信打赏码
支付宝打赏码

欢迎关注D瓜哥的微信公众号,在公众号可以获取我的微信二维码:

微信公众号
公众号的微信号是: jikerizhi。如果图片加载不出来,可以直接通过搜索公众号的微信号来查找D瓜哥的公众号。
来说两句吧...
我来说两句

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK