将内存消耗减少20倍

这将是另一个故事,与我们分享有关内存相关问题的最新经验。 该案例是从最近的客户支持案例中提取的,在该案例中,我们遇到了一个行为异常严重的应用程序,该应用程序因生产中的OutOfMemoryError消息而死亡。 在连接了Plumbr的情况下运行应用程序之后,我们确定这次不会遇到内存泄漏。 但是仍然有一些严重的错误。

症状是通过监视某些数据结构的开销的一项实验功能发现的。 它给了我们一个信号,指出了源代码中的一个特定位置。 为了保护客户的隐私,我们使用合成样本重新制作了案件,同时在技术上使其与原始问题相同。 随时下载源代码 将内存消耗减少20倍

我们发现自己盯着从外部源加载的一组对象。 与外部系统的通信是通过XML接口实现的。 这本身还不错。 但是,集成实现细节分散在整个系统中(将收到的文档转换为XMLBean实例,然后在整个系统中使用)的事实可能并不是最明智的选择。

本质上,我们正在处理延迟加载的缓存解决方案。 缓存的对象是人物:

// Imports and methods removed to improve readability
public class Person {
private String id;
private Date dateOfBirth;
private String forename;
private String surname;
}

不太可能消耗内存。 但是,当我们打开一些更多的细节时,情况看起来会变得有些酸。 也就是说,该数据的实现类似于上面的简单类声明。 相反,该实现使用了模型生成的数据结构。 使用的模型类似于以下简化的XSD代码段:

<xs:schema targetNamespace="http://plumbr.eu"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified">
<xs:element name="person">
<xs:complexType>
<xs:sequence>
<xs:element name="id" type="xs:string"/>
<xs:element name="dateOfBirth" type="xs:dateTime"/>
<xs:element name="forename" type="xs:string"/>
<xs:element name="surname" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>

开发人员使用XMLBeans生成了在后台使用的模型。 现在让我们添加一个事实,即缓存应该容纳多达130万个Persons实例,并且我们为失败奠定了坚实的基础。

运行捆绑的测试用例表明,基于XMLBean的解决方案的130万个实例将消耗大约1.5 GB的堆。 我们认为我们可以做得更好。

第一个解决方案是显而易见的。 集成细节不应跨越系统边界。 因此,我们将缓存解决方案更改为简单的java.util.HashMap <Long,Person>解决方案。 以ID为键,以Person对象为值。 立刻我们发现内存消耗减少到214MB 但是我们还不满意。

由于Map中的键本质上是数字,因此我们有所有理由使用Trove Collections来进一步减少开销。 实现中的快速更改,我们已用TLongObjectHashMap <Person>替换了HashMap 堆消耗降至143MB

我们当然可以在这里停下来,但是出于工程方面的好奇心,我们不允许这样做。 我们不禁注意到所使用的数据包含冗余信息。 出生日期实际上是在ID中编码的,因此我们可以轻松地从给定的ID计算生日,而不是将其复制到其他字段中。

因此,我们更改了Person对象的布局,现在它仅包含以下字段:

// Imports and methods removed to improve readability
public class Person {
private long id;
private String forename;
private String surname;
}

重新运行测试证实了我们的期望。 堆消耗降至93MB 但是我们仍然不满意。

该应用程序在具有旧JDK6版本的64位计算机上运行。 默认情况下不压缩普通对象指针。 切换到-XX:+ UseCompressedOops给了我们额外的胜利-现在我们的内存已减少到73MB

将内存消耗减少20倍

我们可以走得更远,开始实习字符串或基于键构建b树,但这已经开始影响代码的可读性,因此我们决定在这里停止。 21.5倍的堆减少应该已经足够好了。

得到教训?

  • 不要让集成细节跨越系统边界
  • 冗余数据将耗资巨大。 尽可能删除冗余。
  • 原始人是您的朋友。 了解您的工具并学习Trove(如果您还没有的话)
  • 注意JVM提供的优化技术

如果您对进行的实验感到好奇,请随时从此处下载使用的代码 描述了用于测量的实用程序,并在此博客文章中提供了该实用程序。

翻译自: https://www.javacodegeeks.com/2013/06/reducing-memory-consumption-by-20x.html