我必须在 Java 应用程序中查找内存泄漏。我对此有一些经验,但想就此的方法/策略提出建议。欢迎任何参考和建议。
关于我们的情况:
- 堆转储大于 1 GB
- 我们有 5 次堆转储。
- 我们没有任何测试用例来引发这种情况。它仅在使用至少一周后才会在(大规模)系统测试环境中发生。
- 该系统建立在一个内部开发的遗留框架之上,具有如此多的设计缺陷,以至于他们无法将它们全部计算在内。
- 没有人深入了解框架。它已被转移给印度的 一个 人,他几乎没有跟上回复电子邮件的步伐。
- 随着时间的推移,我们已经完成了快照堆转储,并得出结论,没有一个组件会随着时间的推移而增加。一切都在慢慢成长。
- 上面的内容给我们指出了一个方向,那就是框架自己开发的 ORM 系统无限制地增加了它的使用。 (这个系统将对象映射到文件?!所以不是真正的 ORM)
问题: 帮助您成功查找企业级应用程序中的漏洞的方法是什么?
原文由 Rickard von Essen 发布,翻译遵循 CC BY-SA 4.0 许可协议
如果不了解底层代码,这几乎是不可能的。如果您了解底层代码,那么您可以更好地从堆转储中获得的大量信息中挑选出小麦和谷壳。
此外,如果一开始就不知道为什么要上课,就无法知道是否存在泄漏。
过去几周我就是这样做的,我使用了一个迭代过程。
首先,我发现堆分析器基本上没用。他们无法有效地分析巨大的堆。
相反,我几乎完全依赖 jmap 直方图。
我想你对这些很熟悉,但对于那些不熟悉的人:
创建活动堆的直方图。简而言之,它告诉您类名,以及每个类在堆中有多少个实例。
我每天 24 小时每 5 分钟定期倾倒堆。这对您来说可能过于细化,但要点是一样的。
我对这些数据进行了几种不同的分析。
我写了一个脚本来获取两个直方图,并找出它们之间的差异。因此,如果 java.lang.String 在第一次转储中为 10,在第二次转储中为 15,我的脚本将吐出“5 java.lang.String”,告诉我它上升了 5。如果下降了,数将为负。
然后,我将采用其中的几个差异,剔除从运行到运行下降的所有类,并对结果进行并集。最后,我会得到一个在特定时间跨度内不断增长的类列表。显然,这些是泄漏类的主要候选者。
但是,有些类保留了一些,而另一些类则进行了 GC。这些类总体上很容易上下波动,但仍然会泄漏。因此,他们可能会脱离“不断上升”的类别。
为了找到这些,我将数据转换为时间序列并将其加载到数据库中,特别是 Postgres。 Postgres 很方便,因为它提供了 统计聚合函数,因此您可以对数据进行简单的 线性回归分析,并找到趋势上升的类,即使它们并不总是位于图表的顶部。我使用了 regr_slope 函数,寻找具有正斜率的类。
我发现这个过程非常成功,而且非常高效。直方图文件不是特别大,而且很容易从主机上下载。它们在生产系统上运行的成本并不是特别高(它们会强制执行大型 GC,并且可能会暂时阻塞 VM)。我在具有 2G Java 堆的系统上运行它。
现在,这一切所能做的就是识别潜在的泄漏类。
这是理解如何使用这些类,以及它们是否应该成为它们发挥作用的地方。
例如,您可能会发现您有很多 Map.Entry 类或其他一些系统类。
除非您只是简单地缓存 String,否则这些系统类可能并不是“问题所在”,而这些系统类可能是“违规者”。如果您正在缓存某个应用程序类,那么该类可以更好地指示您的问题所在。如果您不缓存 com.app.yourbean,那么您将不会有关联的 Map.Entry 绑定到它。
有了一些类后,您就可以开始爬取代码库以查找实例和引用。由于您拥有自己的 ORM 层(无论好坏),您至少可以轻松地查看它的源代码。如果你的 ORM 正在缓存东西,它很可能会缓存包装你的应用程序类的 ORM 类。
最后,您可以做的另一件事是,一旦您了解了这些类,您就可以启动服务器的本地实例,使用更小的堆和更小的数据集,并使用一个分析器来对抗它。
在这种情况下,您可以进行仅影响您认为可能泄漏的 1 个(或少量)事物的单元测试。例如,您可以启动服务器,运行直方图,执行单个操作,然后再次运行直方图。您泄漏的班级应该增加 1(或者您的工作单元是什么)。
探查器也许能够帮助您跟踪那个“现已泄露”类的所有者。
但是,最后,您将不得不对您的代码库有一些了解,以便更好地理解什么是泄漏,什么不是,以及为什么一个对象存在于堆中,更不用说为什么它可能被保留作为堆中的泄漏。