【问题标题】:Possible try/catch and memory management issue?可能的 try/catch 和内存管理问题?
【发布时间】:2014-07-15 12:28:26
【问题描述】:

我有一个大型 Java 应用程序,它使用 actionPerformed 中的 try/catch 处理大量数据文件(下面的示例代码)。当我在循环中处理大约 1000 个文件时,它会耗尽内存。

每个文件加载合法地占用大约 1MB 的存储空间,但我仔细查看过,并没有看到任何存储该存储空间的地方。每个文件加载都在做同样的事情(即分配相同的变量),所以它应该被重用,而不是累积。

我尝试在循环中插入一个显式 gc 调用,这(根据 visualvm)只能成功消除内存使用中的峰值(见下图)。

奇怪的是内存使用的行为:正如所附图像所显示的那样,在加载循环工作时使用量会攀升,在 try 内持续在高原,但 try 外的 gc 会导致所有内存开垦(高原尽头的悬崖)。

try/catch 是否会影响 gc 行为?关于检查我的代码以查找我可能引入的可能泄漏的任何提示?

我在这方面花了很多时间使用各种内存/堆管理工具和跟踪代码,这真的让我感到困惑。如果这是我的代码中真正的内存泄漏,为什么最终 gc 会清理所有内容?

非常感谢任何建议/想法。

if (message == MenuCommands.TRYLOADINGFILES){
try {
    File dir = new File(<directory with 1015 files in it>);
    File [] cskFiles = dir.listFiles(ioUtilities.cskFileFilter);
    for (int i=0; i<cskFiles.length; i++){
            loadDrawingFromFile(cskFiles[i], true);
            if (i % 10 == 0) System.gc();
    }
    DebugUtilities.pauseForOK("pausing inside try");
}
catch (Exception e1){
    e1.printStackTrace();
}
DebugUtilities.pauseForOK("pausing outside try");
System.gc();
DebugUtilities.pauseForOK("pausing after gc, outside try");
}

在哪里

public static pauseForOK(String msg){
JOptionPane.showMessageDialog(null, msg, "OK", JOptionPane.INFORMATION_MESSAGE);
}

根据 Peter 的建议进行跟进,如下所示。 histo:live 显示几乎没有变化,无论何时运行(在 pgm 启动时,在采取任何操作之前,在读取所有文件之后(当 visualvm 报告正在使用的存储 GB 时),在最终 gc 之后,当 visualvm 说它恢复到初始 stg 使用时)。从启动到运行前四个类别大约加倍,并且 Char stg 的数量增加了大约一个文件处理的预期数量,但其他变化不大。

据它说,它看起来像没有东西粘在周围。这是文件加载循环完成之后(在尝试之外的最终 gc 之前)的 histo 的前 30 行左右。

 num     #instances         #bytes  class name
----------------------------------------------
   1:         67824        9242064  <methodKlass>
   2:         67824        9199704  <constMethodKlass>
   3:          6307        7517424  <constantPoolKlass>
   4:          6307        6106760  <instanceKlassKlass>
   5:         46924        5861896  [C
   6:          5618        4751200  <constantPoolCacheKlass>
   7:         10590        3944304  [S
   8:         19427        3672480  [I
   9:         15280        1617096  [B
  10:         33996        1584808  [Ljava.lang.Object;
  11:          2975        1487144  <methodDataKlass>
  12:         40028        1280896  java.util.Hashtable$Entry
  13:         45791        1098984  java.lang.String
  14:         31079         994528  java.util.HashMap$Entry
  15:         10580         973472  [Ljava.util.HashMap$Entry;
  16:          6750         817344  java.lang.Class
  17:         10427         583912  java.util.HashMap
  18:          1521         523224  javax.swing.JPanel
  19:         10008         516344  [[I
  20:          8291         457176  [Ljava.security.ProtectionDomain;
  21:          4022         431800  [Ljava.util.Hashtable$Entry;
  22:           774         377712  com.sun.java.swing.plaf.windows.WindowsScrollBarUI$WindowsArrowButton
  23:           689         369704  [J
  24:         13931         334344  java.util.ArrayList
  25:          7625         305000  java.util.WeakHashMap$Entry
  26:          8611         275552  java.lang.ref.WeakReference
  27:          8501         272032  java.security.AccessControlContext
  28:         16144         258304  javax.swing.event.EventListenerList
  29:          6141         245640  com.sun.tools.visualvm.attach.HeapHistogramImpl$ClassInfoImpl
  30:           426         245376  <objArrayKlassKlass>
  31:          3937         220472  java.util.Hashtable
  32:         13395         214320  java.lang.Object
  33:          2267         199496  javax.swing.text.html.InlineView

无论它在流程的哪个阶段运行,它基本上都显示相同的内容。即使没有 :live 参数,也得到了基本相同的结果。然而,如果程序在足够多的文件上运行,它肯定会耗尽内存。

另外一项:我使用 visualvm 的 Memory Sampling 拍摄了两张快照,一张在 pgm 启动时,一张在内存使用的高原;增量显示存储使用的预期增长,包括与处理的文件数量完全相同的某些结构的数量增加。由于每个文件处理都会创建其中一个结构,就好像所有中间存储都在 try 中保留,但之后可以清除。

发生了什么事?

++++++++++++

更新 22:00 EDT 星期日

感谢@Peter Lowrey、@Vampire 和其他人的建议。尝试了所有这些想法,但没有任何效果。尝试设置 -XX:NewSize=1GB 和 -XX:NewRatio=3,但没有帮助。

try/catch 是原始代码的保留,并且(我后来意识到)在示例中无关紧要。完全摆脱它不会改变任何事情。只是简单的 for 循环加载文件会导致相同的内存增长模式,然后是 drop 最终 gc 完成后的初始值。

按照@Vampire 的建议,我尝试了这种变体(负载内联,而不是块):

loadDrawingFromFile(thecskFile, true);
loadDrawingFromFile(thecskFile, true);
... 20 times
DebugUtilities.pauseForOK("after 20 loads, before gc");
System.gc();
DebugUtilities.pauseForOK("after gc outside try");

20 个文件加载在已用堆空间(大约 400MB)中按比例产生了与完整示例相同的增长量,然后在上面的 System.gc() 之后,使用的堆空间立即下降到程序初始化级别,和以前一样。

当这种情况发生时,我尝试了一种更基本的方法

loadDrawingFromFile(thecskFile, true);
DebugUtilities.pauseForOK("after load ");
System.gc();
.. repeated 20 times

结果表明,即使在 20 个文件加载后,内存使用量也不会达到 50 MB。

所以这似乎与线程和线程中断有关。这让我再提一个事实:这是一个运行在 GUI 上的应用程序,它的开头是:

SwingUtilities.invokeLater(new Runnable() {
   public void run() { ... }
}

我对线程和 Swing 实用程序不太熟悉,所以这可能是某种形式的幼稚错误,但似乎归结为 gc 没有触及许多非活动对象的事实直到 ShowMessageDialog 打断某些东西。

欢迎提出其他建议。

【问题讨论】:

  • 不要把它放在 try-catch 中。可能是垃圾收集搞砸了。
  • 或者把try catch放到for循环里面...
  • 拜托,你能把代码贴在loadDrawingFromFile里面吗?也许那里有一些有用的东西
  • loadDrawingFromFile的内容为必填项。
  • 保留了哪些对象?尝试` jmap -histo:live {pid} |头 -33`

标签: java memory-leaks garbage-collection try-catch


【解决方案1】:

我猜 Peter 是对的,但万一他不是:您可以通过不关闭 loadDrawingFromFile 中的流来耗尽文件描述符。 IIRC 它也通过 OOM 体现出来,而您可以拥有大量的可用内存。我想这不是你的情况,因为异常消息应该清楚地说明它。

【讨论】:

  • @Maartinus:感谢您的建议,但是使用 visualvm 我可以看到内存从第一个文件加载开始就在无情地增长。所以并不是说我用完了描述符;我最终用完了内存,我可以看到它随着使用的堆大小的增长而出现。
  • @user1359010 那么我猜,我的第一个猜测是正确的。 :)
【解决方案2】:

我怀疑你没有内存泄漏。相反,您过早地提升了大型对象。

如果您正在创建大型对象,例如byte[],这些直接进入终身空间。这些仅在主要或完整集合上进行清理。很可能您只是触发次要收集,在触发完整收集之前不会释放大对象。

【讨论】:

  • @user1359010 您只能使用 System.gc() 触发完整收集;我会考虑回收你的缓冲区或字节[],这可能不是问题。
  • 您不能触发任何类型的垃圾回收。 System.gc() 只是请求虚拟机进行垃圾收集,但不能保证 System.gc() 会产生任何影响。你试过我的建议了吗?
  • @Vampire 虽然不能保证,但实际上 OpenJDK 和 HotSPot JVM 默认每次都会执行一次完整收集。这可以在命令行上更改。
  • @Peter Lawrey:尝试设置 -XX:NewSize=1GB 和 -XX:NewRatio=3,但没有帮助。请参阅上面的原始更新。你还有什么可以建议的吗? Tnx。
  • @user1359010 当您说“它没有帮助”时,我不相信首先存在问题。您的 GC 仅在需要时运行,不清楚是否需要运行。
猜你喜欢
  • 1970-01-01
  • 2011-11-28
  • 1970-01-01
  • 1970-01-01
  • 2012-12-22
  • 2011-02-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多