我怎样才能弄清楚什么是不确定的物体?

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

我点击了JProfiler上的“Garbage Collect”按钮,我们其他类的大部分实例都消失了,但不是这些特殊的。 我再次运行测试,仍然在同一个实例中,它创建了4000多个类的实例,但当我点击“垃圾收集”时,那些离开了8000多个原始实例。

这些实例会在各个阶段陷入各种集合中。 我认为它们不是垃圾收集的事实必然意味着某些东西持有对其中一个集合的引用,以便保持对对象的引用。

有什么建议我如何弄清楚什么是持有参考? 我正在寻找在代码中寻找什么的建议,以及如果有的话在JProfiler中找到它的方法。

转储堆并检查它。

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

  1. 如果您还没有安装JDK,请安装它。 它配备了一堆巧妙的工具。
  2. 启动应用程序。
  3. 打开任务管理器,找到java.exe(或您正在使用的任何可执行文件)的进程ID(PID)。 如果默认情况下未显示PID,请使用“视图”>“选择列…”添加它们。
  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 []类型的对象

试试Eclipse Memory Analyzer。 它将向您显示每个对象如何连接到GC根 – 一个非垃圾收集的对象,因为它由JVM保存。

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

我会在你的课程中看看Collections(特别是静态的)(HashMaps是一个很好的起点)。 以此代码为例:

 Map map = new HashMap(); // 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或其他一些集合具有对该对象的引用,它就不会被垃圾回收。

在那里没有银弹,您必须使用探查器来识别容纳那些不需要的对象的集合,并在代码中找到它们应该被移除的位置。 正如JesperE所说,静态collections是第一个看的地方。

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

还要注意,你可以获得一个OOME,因为gc无法收集足够的内存,尽管实际上有足够的对象请求被创建。 否则,表演将陷入困境。

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

编辑:删除了WeakReference上的错误备注。

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

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

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

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

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

我已经使用Yourkit Java分析器( 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,
〜一

collections已经提到了。 另一个难以找到的位置是你使用多个ClassLoader,因为旧的类加载器可能无法被垃圾收集,直到所有引用都消失。

还要检查静力学 – 这些都是令人讨厌的。 日志框架可以保持打开状态,这可以保留自定义appender中的引用。

你解决了这个问题吗?

一些建议:

  • 无限制的地图用作缓存,尤其是在静态时
  • 服务器应用程序中的ThreadLocals,因为线程通常不会死,所以ThreadLocal不会被释放
  • 实习字符串(Strings.intern()),它在PermSpace中产生一堆字符串

如果您在垃圾收集语言中收到OOM错误,通常意味着收集器不会记录某些内存。 也许你的对象持有非java资源? 如果是这样,那么他们应该有某种“关闭”方法,以确保即使没有尽快收集Java对象也释放资源。