15

我们的一个程序有时会OutOfMemory在一个用户的机器上出错,但在我测试它时当然不会。我只是用 JProfiler 运行它(在 10 天评估许可证上,因为我以前从未使用过它),并过滤我们的代码前缀,总大小和实例数的最大块是特定简单类的 8000+ 个实例.

我单击了 JProfiler 上的“垃圾收集”按钮,我们其他类的大多数实例都消失了,但这些特定的实例没有。我再次运行测试,仍然在同一个实例中,它创建了 4000 多个该类的实例,但是当我单击“垃圾收集”时,这些实例消失了,留下了 8000 多个原始实例。

这些实例确实在各个阶段陷入了各种集合中。我假设它们不是垃圾收集的事实一定意味着某些东西正在持有对其中一个集合的引用,因此它持有对对象的引用。

有什么建议可以让我弄清楚参考资料是什么?我正在寻找有关在代码中寻找什么的建议,以及在 JProfiler 中找到它的方法(如果有)。

4

11 回答 11

20

转储堆并检查它。

我确信有不止一种方法可以做到这一点,但这里有一个简单的方法。此描述适用于 MS Windows,但可以在其他操作系统上执行类似的步骤。

  1. 如果您还没有 JDK,请安装它。它带有一堆整洁的工具。
  2. 启动应用程序。
  3. 打开任务管理器并找到 java.exe(或您正在使用的任何可执行文件)的进程 ID (PID)。如果默认情况下不显示 PID,请使用 View > Select Columns... 添加它们。
  4. 使用jmap转储堆。
  5. 在您生成的文件上启动jhat服务器并打开浏览器访问http://localhost:7000(默认端口为 7000)。现在您可以浏览您感兴趣的类型以及实例数量、引用它们的内容等信息。

这是一个例子:

C:\dump>jmap -dump:format=b,file=heap.bin 3552

C:\dump>jhat heap.bin
Reading from heap.bin...
Dump file created Tue Sep 30 19:46:23 BST 2008
Snapshot read, resolving...
Resolving 35484 objects...
Chasing references, expect 7 dots.......
Eliminating duplicate references.......
Snapshot resolved.
Started HTTP server on port 7000
Server is ready.

为了解释这一点,了解 Java 使用的一些数组类型命名法很有用——比如知道类 [Ljava.lang.Object; 真正意味着Object[]类型的对象。

于 2008-09-30T19:28:17.737 回答
10

试试 Eclipse 内存分析器。它将向您展示每个对象如何连接到 GC 根 - 一个不会被垃圾回收的对象,因为它由 JVM 持有。

有关 Eclipse MAT 如何工作的更多信息,请参阅http://dev.eclipse.org/blogs/memoryanalyzer/2008/05/27/automated-heap-dump-analysis-finding-memory-leaks-with-one-click/

于 2008-09-30T19:26:21.127 回答
5

我会在你的类中查看集合(尤其是静态集合)(HashMaps 是一个很好的起点)。以这段代码为例:

Map<String, Object> map = new HashMap<String, Object>(); // 1 Object
String name = "test";             // 2 Objects
Object o = new Object();          // 3 Objects
map.put(name, o);                 // 3 Objects, 2 of which have 2 references to them

o = null;                         // The objects are still being
name = null;                      // referenced by the HashMap and won't be GC'd

System.gc();                      // Nothing is deleted.

Object test = map.get("test");    // Returns o
test = null;

map.remove("test");               // Now we're down to just the HashMap in memory
                                  // o, name and test can all be GC'd

只要 HashMap 或其他一些集合有对该对象的引用,它就不会被垃圾收集。

于 2008-09-30T18:58:33.547 回答
3

那里没有灵丹妙药,您必须使用分析器来识别包含那些不需要的对象的集合,并在代码中找到应该删除它们的位置。正如 JesperE 所说,静态集合是首先要看的地方。

于 2008-09-30T18:44:26.187 回答
2

留意静态容器。只要加载了类,静态容器中的任何对象都将保留。

编辑:删除了对 WeakReference 的不正确评论。

于 2008-09-30T18:35:32.900 回答
2

一个明显的候选对象是带有终结器的对象。他们可以在调用 finalize 方法时逗留。它们需要被收集,然后最终确定(通常只使用一个终结器线程),然后再次收集。

另请注意,您可以获得 OOME,因为 gc 未能收集足够的内存,尽管实际上有足够的内存来创建对象请求。否则性能会被磨平。

于 2008-09-30T18:44:23.440 回答
1

我刚刚读过一篇关于这个的文章,但很抱歉我不记得在哪里。我认为它可能在“Effective Java”一书中。如果我找到参考资料,我会更新我的答案。

它概述的两个重要教训是:

1)最终方法告诉gc在剔除对象时要做什么,但它没有要求它这样做,也没有办法要求它这样做。

2)现代非托管内存环境中的“内存泄漏”等价物是被遗忘的引用。如果您在使用完对象后没有将所有对对象的引用设置为null,则该对象将永远不会被剔除。这在实现您自己的 Collection 或您自己的管理 Collection 的包装器时最重要。如果您有一个池、一个堆栈或一个队列,并且当您从集合中“删除”一个对象时没有将存储桶设置为null,则该对象所在的存储桶将使该对象保持活动状态,直到该存储桶设置为引用另一个对象。

免责声明:我知道其他答案提到了这一点,但我试图提供更多细节。

于 2008-09-30T19:20:18.533 回答
1

我使用 Yourkit Java profiler ( http://www.yourkit.com ) 对 java 1.5 进行性能优化。它有一个关于如何处理内存泄漏的部分。我觉得它很有用。

http://www.yourkit.com/docs/75/help/performance_problems/memory_leaks/index.jsp

您可以获得 15 天的评估:http: //www.yourkit.com/download/yjp-7.5.7.exe

BR,
~A

于 2008-09-30T19:30:57.307 回答
1

收藏品已经提到过。另一个难以找到的位置是,如果您使用多个 ClassLoader,因为在所有引用都消失之前,旧的 classloader 可能无法被垃圾收集。

还要检查静态数据 - 这些很讨厌。日志框架可以保持开放,这可能会在自定义附加程序中保留引用。

你解决问题了吗?

于 2009-02-09T07:24:17.417 回答
1

一些建议:

  • 无限地图用作缓存,尤其是在静态时
  • 服务器应用中的 ThreadLocals,因为线程通常不会死掉,所以 ThreadLocal 没有被释放
  • Interning 字符串(Strings.intern()),导致 PermSpace 中有一堆字符串
于 2009-03-31T01:51:06.720 回答
0

如果您在垃圾收集语言中遇到 OOM 错误,这通常意味着收集器没有占用一些内存。也许您的对象拥有非 java 资源?如果是这样,那么他们应该有某种“关闭”方法来确保即使没有足够快地收集 Java 对象也能释放资源。

于 2008-09-30T18:37:01.760 回答