java可能的try/catch和内存管理问题?
我有一个大型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);
}
根据彼得的建议采取后续行动,如下所示。histo:无论何时运行(在pgm启动时,在采取任何操作之前,在读取所有文件之后(当visualvm报告正在使用的存储容量为GB时),在最终gc之后,当visualvm表示恢复到初始stg使用时),live都几乎不显示任何更改。从启动到运行前四个类别大约是原来的两倍,Char stg的数量增加了大约一个文件处理的预期数量,但其他方面没有太大变化
根据它的说法,看起来什么都没有。下面是文件加载循环完成后(在try之外的最终gc之前)的大约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
它基本上显示了相同的东西,无论它在流程的哪个点运行。即使没有现场辩论,结果也基本相同。然而,如果程序运行在足够多的文件上,它肯定会耗尽内存
还有一项:我用visualvm的记忆取样拍摄了两张快照,一张是在pgm启动时拍摄的,另一张是在记忆使用平稳期拍摄的;增量显示了存储使用的预期增加,包括某些结构的数量增加,这与处理的文件数量完全相同。当每个文件处理创建其中一个结构时,就好像所有中间存储都在try内部保留,但可以在之后清除
怎么回事
++++++++++++
美国东部时间周日22:00更新
感谢@Peter Lowrey、@Vampire和其他人的建议。尝试了所有这些想法,但没有任何效果。尝试设置-XX:NewSize=1GB和-XX:NewRatio=3,但没有帮助
try/catch是对原始代码的保留,并且(我后来才意识到)与示例无关。摆脱它完全改变不了什么。加载文件的简单for循环会导致相同的内存增长模式,然后是内存下降 当最终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)的增长量与完整示例中的增长量成比例,然后是在系统运行之后。如上所述,使用的堆空间会立即下降到程序初始化级别,就像以前一样
当这种情况发生时,我尝试了一种更基本的方法
loadDrawingFromFile(thecskFile, true);
DebugUtilities.pauseForOK("after load ");
System.gc();
.. repeated 20 times
结果证明这是可行的,因为即使在20个文件之后,内存使用量也不会达到50MB负载
所以这似乎与线程和线程中断有关。这让我又提到了一个事实:这是一个运行在GUI上的应用程序,该GUI以:
SwingUtilities.invokeLater(new Runnable() {
public void run() { ... }
}
我对线程和Swing实用程序不太熟悉,所以这可能是某种形式的天真错误,但这似乎归结为一个事实,即许多非活动对象在ShowMessageDialog中断某些内容之前不会被gc触及
欢迎提出更多建议
# 1 楼答案
我猜Peter是对的,但如果他不是这样的话:如果不关闭
loadDrawingFromFile
中的流,文件描述符可能会用完。IIRC它也通过OOM表现出来,而你可以有大量的空闲内存。我想这不是你的情况,因为例外信息应该清楚地说明这一点