如果不了解底层代码,这几乎是不可能的。如果您了解底层代码,则可以更好地将小麦与堆转储中获取的大量信息的谷壳进行排序。
此外,您无法知道某些内容是否是泄漏,而不知道为什么该类首先存在。
在过去的几周里,我只是在做这件事,我使用了一个迭代过程。
首先,我发现堆分析器基本上没用。他们无法有效地分析巨大的堆。
相反,我几乎完全依赖于jmap直方图。
我想你熟悉这些,但对于那些不熟悉的人:
jmap -histo:live <pid> > histogram.out
创建活动堆的直方图。简而言之,它会告诉您类名,以及堆中每个类的实例数。
我定期倾倒堆,每5分钟,每天24小时。这对你来说可能太细化了,但要点是一样的。
我对这些数据进行了几次不同的分析。
我写了一个脚本来获取两个直方图,并转储它们之间的差异。所以,如果java.lang.String在第一个转储中是10,在第二个转储中是15,我的脚本会吐出“5 java.lang.String”,告诉我它上升了5。如果它下降了,这个数字将是负数。
然后,我会选取其中的几个差异,去掉从一个运行到运行的所有类,并取一个结果的并集。最后,我会有一个在特定时间跨度内不断增长的类列表。显然,这些是泄漏类的主要候选者。
但是,某些类保留了一些,而其他类则保留了一些。这些类在总体上很容易上下波动,但仍然会泄漏。因此,他们可能会从“不断上升”的类别中掉出来。
为了找到这些,我将数据转换为时间序列并将其加载到数据库中,特别是Postgres。Postgres很方便,因为它提供了统计聚合函数,因此您可以对数据进行简单的线性回归分析,并找到趋势上升的类,即使它们并不总是在图表的顶部。我使用了regr_slope函数,寻找具有正斜率的类。
我发现这个过程非常成功,而且非常有效。直方图文件并不是非常大,从主机下载它们很容易。它们在生产系统上运行的成本并不高(它们确实会强制使用大型GC,并且可能会阻塞VM一段时间)。我在一个具有2G Java堆的系统上运行它。
现在,所有这一切可以做的就是识别潜在的泄漏类。
这就是了解如何使用类,以及它们是否应该成为它们的地方。
例如,您可能会发现您有很多 Map.Entry 类或其他一些系统类。
除非你只是简单地缓存 String,否则事实是这些系统类,虽然可能是“违规者”,但不是“问题”。如果要缓存某个应用程序类,则该类可以更好地指示问题所在。如果您不缓存 com.app.yourbean,则不会将关联的 Map.Entry 绑定到它。
拥有一些类后,可以开始对代码库进行爬网以查找实例和引用。由于您有自己的ORM层(无论好坏),您至少可以很容易地查看它的源代码。如果你的ORM正在缓存东西,那么它可能会缓存ORM类来包装你的应用程序类。
最后,您可以做的另一件事是,一旦您知道了这些类,您就可以启动服务器的本地实例,其中包含更小的堆和更小的数据集,并使用其中一个探查器来对付它。
在这种情况下,您可以进行单元测试,该测试仅影响您认为可能泄漏的1个(或少量)内容。例如,您可以启动服务器,运行直方图,执行单个操作,然后再次运行直方图。你泄漏的类应该增加了1(或者你的工作单位是什么)。
探查器可能能够帮助您跟踪“现已泄露”类的所有者。
但是,最终,您必须对代码库有一定的了解,以便更好地了解什么是泄漏,什么不是,以及为什么某个对象存在于堆中,更不用说为什么它可能作为泄漏保留在堆中。