当并非所有已用堆都可以从线程访问时查找 Java 内存泄漏

2022-09-02 04:44:53

我正在研究基于Java的大型系统中潜在的内存泄漏(或至少是内存浪费)。JVM 正在运行,最大堆大小为 5 GB,2-3GB 堆使用率是应用程序的预期基准线。(可能会有更高的峰值)

在我正在调查的过载场景中,堆被填满了。使用“Eclipse MemoryAnalyzer Tool”分析堆转储显示(不足为奇)堆完全用完。

MAT显示了2个潜在的泄漏候选者,两个都大致保留了2.5GB:java.lang.Thread和系统中的域对象,该对象在系统中的事务处理过程中广泛使用。但是,所有这些域对象都可以从 Thread 实例访问(不足为奇)。毕竟,这些线程正在处理事务。因此,归因于java.lang.Thread的2.5 GB几乎完全是由这些域对象引起的。这并不奇怪。

列出所有 java.lang.Thread 实例的对象树,并对所有线程的保留堆求和,得到 2.5 GB 的保留堆。

如果无法从 java.lang.Thread 的实例访问其他 2.5 GB,我应该在哪里查找填充堆所需的其他 2.5 GB?- 终结器队列中没有任何内容 - 没有大量无法访问的对象等待 GC

我认为提出这个问题的另一种方法是:“如何找到所有无法从java.lang.Thread实例访问的对象?也许是一个 OQL 查询?,另一个问题是:“有哪些类型的对象无法从 java.lang.Thread 的实例访问,然后是终结器队列中的对象和未引用的对象挂起的 GC?


答案 1

我也在我们的网站上遇到了内存泄漏的问题,
使用yourkit java profiler,它提供了大量信息,并且凭借其功能,您可以在所有内存被利用的地方拥有更广泛的图像。
您可以使用上述工具找到一个很棒的教程查找Java内存泄漏

你的问题,

“还有哪些类型的对象无法从 java.lang.Thread 的实例访问,然后是终结器队列中的对象和未引用的对象挂起的 GC?”

物体有四种,

  1. 强大的可访问对象,可以通过来自实时对象的引用直接访问的对象
  2. 弱/软可访问,具有弱/软引用的对象
  3. 挂起的终结,等待终结的对象,其引用可以通过终结器队列访问
  4. 无法访问这些对象无法从 GC 根目录访问,但尚未收集

除了这些JVM还使用本机内存,您可以在IBM堆上找到其信息,并通过JVM感谢内存使用本机内存,根据YourKit,JVM内存结构具有非堆内存,其定义是

此外,JVM 还具有堆以外的内存,称为非堆内存。它是在 JVM 启动时创建的,用于存储每个类的结构,例如运行时常量池、字段和方法数据、方法和构造函数的代码以及滞留字符串。


答案 2

由于额外的内存没有显示在MAT中,因此很难知道该建议什么。我很抱歉,如果其中一些(甚至大多数)是你已经知道的事情,我只是试图把我能想到的一切放在一起。

查找虫

FindBugs是一个静态分析工具,它将扫描您的代码,寻找常见的反模式和问题,并为您提供有关它们的漂亮报告。它确实发现了许多潜在内存和资源泄漏的原因。

手动转储

你可以尝试使用像jmap或visualvm这样的东西来手动获取堆转储进行分析,看看你是否从让eclipse这样做中获得不同的结果:

http://docs.oracle.com/javase/1.5.0/docs/tooldocs/share/jmap.html

http://java.dzone.com/articles/java-heap-dump-are-you-task

分析器怪癖

内存分析器常见问题解答:

http://wiki.eclipse.org/MemoryAnalyzer/FAQ

说:

症状:以交互方式监视内存使用情况时,已用堆大小比 MAT 报告的大小大得多。

在索引创建期间,内存分析器会删除无法访问的对象,因为各种垃圾回收器算法往往会留下一些垃圾(如果对象太小,移动和重新分配地址的成本很高)。然而,这应该不超过3%到4%。如果您想知道删除了哪些对象,请启用调试输出,如下所述:MemoryAnalyzer/FAQ#Enable_Debug_Output

另一个原因可能是堆转储未正确编写。特别是较旧的 VM(1.4、1.5)如果堆转储是通过 jmap 编写的,则可能会出现问题。

启用调试输出将允许您查看那里发生了什么,并确认该区域没有任何奇怪之处。

其中一些提示可能相关

http://eclipsesource.com/blogs/2013/01/21/10-tips-for-using-the-eclipse-memory-analyzer/