一个Java swing桌面应用程序的JVM内存调优
背景介绍
用java swing(jdk 1.7.0_71)开发了一款类似于windows explorer的桌面产品,除了文件管理等常用的功能外,还附带了标签(TAG)管理功能,也就是对文件或者文件夹指定标签,根据标签对文件进行管理.
遇到的问题
应用中有一个根据关键字进行文件查找的功能,查找本身并没有什么问题,只是对符合查找条件的文件或者文件夹进行画面显示的时候,遇到了jvm内存爆满的问题.因为测试了一种极端的情况,查找C盘下的所有带a关键字的文件和文件夹,因为结果集特别大,所以画面显示的时候遭遇了JVM内存储爆满.
原因分析
结果集显示的时候,其实就是往JPanel里面不断地add新的文件JPanel,一个文件JPanel里面至少有两个JLabel,一个用来显示图标,另一个显示文件名,所有的这些对象都在不断地向JVM堆里面涌动,而且这些对象在切换到别的画面之前,都不会被JVM回收.这样就造成了JVM内存储爆满.
整个过程如下:
1>缺省配置启动java,最大堆内存为247.5M,这个数值是绝对不够用的
因为
NewRatio=2 default
SurvivorRatio=8 default
所以
Eden space=64M
Survivor space=8M
Tenured gen=175.5M
最终结果如下:
Console报错:
Exception in thread "RMI TCP Connection(idle)" Exception in thread "RMI TCP Connection(idle)" java.lang.OutOfMemoryError: Java heap space
Exception in thread "AWT-EventQueue-0" java.lang.OutOfMemoryError: Java heap space
Exception in thread "AWT-EventQueue-0" java.lang.OutOfMemoryError: Java heap space
Exception in thread "AWT-EventQueue-0" java.lang.OutOfMemoryError: Java heap space
Exception in thread "AWT-EventQueue-0" java.lang.OutOfMemoryError: Java heap space
Exception in thread "AWT-EventQueue-0" java.lang.OutOfMemoryError: Java heap space
Exception in thread "AWT-EventQueue-0" java.lang.OutOfMemoryError: Java heap space
Exception in thread "AWT-EventQueue-0" java.lang.OutOfMemoryError: Java heap space
Exception in thread "JMX server connection timeout 23" java.lang.OutOfMemoryError: Java heap space
java.lang.NullPointerException
at sun.awt.SunToolkit.postEvent(SunToolkit.java:473)
at sun.awt.windows.WComponentPeer.postEvent(WComponentPeer.java:849)
2>把最大堆内存调整到上限1024M
-XX:+PrintGCDetails -Xms1024m -Xmx1024m
实际获得的heap合计:990M
因为
NewRatio=2 default
SurvivorRatio=8 default
所以
Eden space=273M
Survivor space=34M
Tenured gen=683M
最终,成功执行完查找操作,只是很勉强(Tenured gen只剩下了1K)
结果如下:
3>从2>的结果可以看出,问题的关键在于Tenured gen的空间大小,尽可能地把空间分给Tenured gen是解决问题的关键,于是,我们可以追加-XX:NewRatio参数来调大Tenured gen的空间,比如:
-XX:+PrintGCDetails -Xms1024m -Xmx1024m -XX:NewRatio=4
因为绝大部分对象都是无法回收的,所以为了降低gc时间,其实我们也可以通过调大-XX:SurvivorRatio的值,变相拿掉Survivor space,比如:
-XX:+PrintGCDetails -Xms1024m -Xmx1024m -XX:NewRatio=4 -XX:SurvivorRatio=65536
4>之前使用的gc属于单线程的serial collector,比较适合于单机,如果想尝试一下CMS并发收集器的话,也是可以的,虽然必要性不大
-XX:+PrintGCDetails
-Xms1024m -Xmx1024m
-Xmn200m 直接指定年轻代大小,也可以通过-XX:NewRatio调节
-XX:SurvivorRatio=65536 变相去掉Survivor space
-XX:+UseConcMarkSweepGC 使用CMS内存收集
-XX:ReservedCodeCacheSize=10m 尽可能节省空间
-XX:MaxPermSize=20m 尽可能节省空间
-XX:+CMSScavengeBeforeRemark 强制remark之前开始一次minor gc,减少remark的暂停时间,不过我们这种情况没有必要加,因为大部分对象都不会被gc掉
-XX:+UseCMSInitiatingOccupancyOnly 为了配合CMSInitiatingOccupancyFraction使用,不指定的话,jvm根据历史数据自动判断什么时候进行gc
-XX:CMSInitiatingOccupancyFraction=95 Tenured gen代使用空间达到95%才进行gc