【发布时间】: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