小编典典

在大型Java堆转储中查找内存泄漏的方法

java

我必须在Java应用程序中找到内存泄漏。我对此有一些经验,但希望就此方法论/战略提出建议。欢迎任何参考和建议。

关于我们的情况:

  1. 堆转储大于1 GB
  2. 我们有5次堆放场。
  3. 我们没有任何测试案例可以激发这一点。仅在使用至少一个星期后,它才会在(大规模)系统测试环境中发生。
  4. 该系统建立在内部开发的传统框架上,该框架具有许多设计缺陷,以至于无法将它们全部计算在内。
  5. 没有人深入了解该框架。它已被转移到 一个 在印度的家伙谁勉强回复电子邮件保存起来。
  6. 我们已经完成了快照堆转储,并得出结论,没有一个组件随时间增加。一切都是缓慢增长的。
  7. 上面的内容为我们指明了一个方向,那就是框架自有的ORM系统无限制地增加了其使用率。(此系统将对象映射到文件吗?!实际上不是ORM)

问题: 什么方法可以帮助您成功解决企业级应用程序中的漏洞?


阅读 219

收藏
2020-11-01

共1个答案

小编典典

如果不了解底层代码,这几乎是不可能的。如果您了解基础代码,那么您可以更好地根据堆转储中获得的成千上万的信息来区分小麦。

同样,您不知道某个类为何存在,就无法知道是否存在泄漏。

我刚刚花了过去的两周时间来完成这项工作,并且我使用了一个迭代过程。

首先,我发现堆分析器基本上没有用。他们无法有效地分析巨大的堆。

相反,我几乎完全依靠jmap直方图。

我想您对这些很熟悉,但对于那些不是:

jmap -histo:live <pid> > dump.out

创建活动堆的直方图。简而言之,它告诉您类名以及堆中每个类有多少个实例。

我每天每隔5分钟,每天24小时定期倾倒垃圾。对于您来说,这可能太细微了,但要旨是相同的。

我对此数据进行了几种不同的分析。

我编写了一个脚本,以获取两个直方图,并转储它们之间的差异。因此,如果第一个转储中的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(或任何工作单位)。

探查器可能可以帮助您跟踪“现在泄露”的类的所有者。

但是,最后,您将必须对代码库有所了解,以便更好地了解什么是泄漏,什么不是泄漏以及为什么对象在堆中根本存在,而更少地说明为什么可以保留它作为堆中的泄漏。

2020-11-01