进来编写的一个程序在长时间运行后似乎会停止响应,想到了是否是内存泄漏的缘故。之前没有好好研究过Java内存泄漏的问题,所以首先搜索了一下网络,结合Jprofiler工具,想先对Java Application的内存泄漏问题作一下研究。
通过搜索,网上所举的关于内存泄漏的问题其中一个为:
释放不用的对象:参见《java内存泄漏》,了解JVM的工作原理及机制规范;
ArrayList al = new ArrayList();
for(int i = 0;i<200;i++){
Object o = new Object();
o.name = 1;
o.id = 1;
al.add(o);
}
以上代码中o仍被al引用,没有释放,不会被回收。因此需修正:
Arraylist al = new Arraylist();
for(int i = 0;i<200;i++){
Object o = new Object();
o.name = 1;
o.id = 1;
al.add(o);
o = null;
}
注意上面红色字体部分是错误的。而修正中的 o=null; 更是画蛇添足。因为o本来就是一个局部变量,每次循环时都会去引用一个新的对象,而且再离开复合语句时o就不存在了,但是由于对象被加入了集合ArrayList中,因此对象还是被引用的,并不会被回收。
为了在JProfiler中查看对象分配和回收的情况,我写了下面的两个类
package my;
public class MyObject {
public int name;
public int id;
}
package my;
import java.io.IOException;
import java.util.ArrayList;public class Test2 {
public static void main(String[] args) throws IOException {
char ch = ' ';
while (ch != 'y')
ch = (char) System.in.read();
ArrayList<MyObject> al = new ArrayList<MyObject>();
for (int i = 0; i < 200; i++) {
MyObject o = new MyObject();
o.name = 1;
o.id = 1;
al.add(o);
o = null;
}
ch = ' ';
while (ch != 'n')
ch = (char) System.in.read();
al=null;
ch = ' ';
while (ch != 'n')
ch = (char) System.in.read();
}
}
经过分析,程序在运行时,上述代码中蓝色部分 o=null; 加与不加,对对象的分配和回收是没有任何影响的。而红色的 al=null; 如果不加的话,那么在程序中在这一点明显可以看到内存是不能回收的,但是,由于al本身是一个局部变量,因此在main()运行结束时,al的生命周期结束,它所引用的对象都会被释放。
在 江南白衣所写的《编写对GC友好,又不泄漏的代码》 (最新版链接:http://blog.csdn.net/calvinxiu/archive/2007/05/22/1621051.aspx)中,提到了这样几条原则:
1.使用更多生命周期短的、小的、不改变指向(immutable)的对象,编写清晰的代码。
出于懒惰也好,朴素的节俭意识也好,我们都习惯对一个变量重用再重用。但是....
- Java的垃圾收集器喜欢短生命周期的对象,对象如果在新生代内,在垃圾收集发生前就死掉了,垃圾收集器就什么都不用做了。
- 现代JVM构建一个新对象只需要10个本地CPU指令,并不弱于C/C++。 (但垃圾收集没有压缩算法时会稍慢,更频繁的New对象也导致更频繁的GC)。
- 大对象的分配效率更低,而且对非压缩算法的垃圾收集器,更容易造成碎片。
- 对象重用增加了代码的复杂度,降低了可读性。
所以有标题的呼吁,比如不要害怕为中间结果分配小对象。但编程习惯的改变也不是一朝一夕的事情。
2.将用完的对象设为NULL其实没什么作用。
貌似很酷的把对象主动设为Null 的"好习惯"其实没什么用,JIT Compiler会自动分析local变量的生命周期。
只有一个例外情况,就是String[1024] foo 这种赤裸裸的数组,你需要主动的foo[100]=null释放第100号元素,所以最好还是直接用ArrayList这些标准库算了。3.避免显式GC--System.gc()。
大家都知道System.gc()不好,full-gc浪费巨大,gc的时机把握不一定对等等,甚至有-XX:+DisableExplicitGC的JVM参数来禁止它。
哈哈,但我还不会用System.gc()呢,不怕不怕。真的不怕吗?
- 先用FindBugs 查一下所用到的全部第三方类库吧...
- 至少RMI 就会老实不客气的执行System.gc()来实现分布式GC算法。但我也不会用RMI啊。那EJB呢,EJB可是建在RMI上的....
如果无可避免,用-Dsun.rmi.dgc.client.gcInterval=3600000 -Dsun.rmi.dgc.server.gcInterval=3600000 (单位为微妙) 增大大GC的间隔(原默认值为1分钟),-XX:+ExplicitGCInvokesConcurrent 让System.gc() 也CMS并发执行。
4.继续千夫所指的finalize()
大家也都知道finalize()不好,分配代价昂贵,释放代价更昂贵(要多走一个循环,而且他们死得慢,和他们相关联的对象也跟着死得慢了),又不确定能否被调用(JVM开始关闭时,就不会再进行垃圾收集),又不确定何时被调用(GC时间不定,即使system.gc()也只是提醒而不是强迫GC,又不确定以什么样的顺序调用,所以finalize不是C++的析构函数,也不像C++的析构函数。
我们都知道啊,所以我从来都没使用。都是在显式的维护那些外部资源,比如在finally{}里释放。
在上面的例子中,确实也如白衣的文章所述,一般情况下没有必要将对象设置为null。
另外,一些文章中提到了使用ArrayList的方法clear()来清除ArrayList对对象的引用,该方法的源代码如下,实际上就是将其中的每个引用变量设置为null,断开对对象的引用。
/**
* Removes all of the elements from this list. The list will
* be empty after this call returns.
*/
public void clear() {
modCount++;// Let gc do its work
for (int i = 0; i < size; i++)
elementData[i] = null;size = 0;
}
而前面的代码已经说明,对ArrayList对象消亡的时候,所引用的对象也会被回收,所以这个clear方法一般不需要调用。
使用更多生命周期短的、小的、不改变指向(immutable)的对象,编写清晰的代码--------------欣赏。
回复删除