内存管理往往是很多应用程序的关键部分,像在C/C++这些需要手动管理内存的语言中编程,稍有缺陷就有可能导致大量内存泄漏,从而使程序崩溃。Java提供了一个自动回收内存的垃圾收集器,让Java程序员能从内存管理中解脱出来,但是也存在缺点,那就是不能完全控制它什么时候执行什么时候不执行。
垃圾回收器,通常就叫它GC,GC实际上是一个受JVM控制的程序,它的核心任务就是删除Java程序运行不再触及的任何对象。所谓的“触及”是指Java程序中任何一个活动的线程存在对这个对象的引用。
GC是什么时候运行呢?
GC受JVM的控制,JVM决定什么时候运行GC,我们平常可以在程序中请求JVM进行垃圾回收,从而释放更多的内存供程序使用,但是在任何情况下,请求都无法保证JVM会答应我们的请求,当我们请求回收时,短期内JVM会进行垃圾回收,但这没有任何保障。JVM会在它觉得必要的时候进行回收,如下面第一段和第二段代码,虽然第二段代码执行次数更多,但GC觉得内存不够了,它进行了回收,到最后占用的内存反而比第一段的代码占用的内存少。第三段代码我们可以看得到我们请求GC时还我内存的效果。System.gc(),看源码可以知道,实际上它调用了Runtime 的gc()方法。
public static void main(String[] args) {
Runtime rt = Runtime.getRuntime();
System.out.println("Total JVM memory:" + rt.totalMemory());
System.out.println("Before Memory=" + rt.freeMemory());
Date d = null;
for(int i=0;i<100000;i++){
d = new Date();
d = null;
}
System.out.println("After Memory=" + rt.freeMemory());
rt.gc();
System.out.println("After GC Memory=" + rt.freeMemory() );
System.out.println("=======================");
System.out.println("Before Memory=" + rt.freeMemory());
for(int i=0;i<10000000;i++){
d = new Date();
d = null;
}
System.out.println("After Memory=" + rt.freeMemory());
rt.gc();
System.out.println("After GC Memory=" + rt.freeMemory() );
System.out.println("=======================");
System.out.println("Before Memory=" + rt.freeMemory());
Date[] ds2 = new Date[1000000];
for(int i=0;i<1000000;i++){
ds2[i] = new Date();
}
System.out.println("After Memory=" + rt.freeMemory());
rt.gc();
System.out.println("After GC Memory=" + rt.freeMemory() );
}
执行结果:
Total JVM memory:5177344
Before Memory=4945624
After Memory=4254216
After GC Memory=5024744
=======================
Before Memory=5024744
After Memory=4197480
After GC Memory=5020280
=======================
Before Memory=5020280
After Memory=18211424
After GC Memory=46200736
请求GC的时候,JVM往往并不会马上响应我们的要求,finalize()方法是每一个对象回收时都会运行一次的方法,并且仅一次,一般不要重写finalize()方法,在此方法中可以让该需要回收的对象起死回生,让它重新被引用,从而让该对象免于被回收,但是此方法只执行一次,当下次该对象符合回收条件的时候,JVM会判断已经执行过finalize()了,将不再执行了。运行如下代码,你会发现请求并不那么有效,被回收的对象也不是那么有先后顺序了。
public class FinalizeTest {
int num;
public FinalizeTest(int num){
this.num = num;
}
/**
* @author ZhangXiang
* @param args
* 2010-1-15
*/
public static void main(String[] args) {
FinalizeTest ft = null;
for(int i=0;i<100000;i++){
ft = new FinalizeTest(i);
ft = null;
System.gc();
}
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("FinalizeTest"+this.num);
}
}
GC是怎么样工作的呢?
首先我们得要了解垃圾回收的条件,前面也提到:当没有任何活的线程能够访问一个对象时,该对象就符合垃圾回收的条件了,有一个例外,String存在于String常量池中,虽然它也是对象,但我们感觉它更像常量一样,表现更反复无常。虽然我们没办法完全控制GC工作,但是我们还是能适当指引它运行,为其回收对我们没用的对象创造条件:
1、空引用
很简单,只需要把引用我们不需要的对象的变量设置为Null,如下面的代码
Date date = new Date();
Sytem.out.println(date);
date = null;
2、为变量重新赋值
就是给引用垃圾对象的变量重新赋一个值,如下面的代码第一次new得到的象在变量被赋予一个新对象的时候就已经符合回收的条件了,当然等待它的也就是回收的命运了
Date date = new Date();
Sytem.out.println(date);
date = new Date();
3、隔离引用
当对象中的属性对其它对象进行交叉引用,如果两个对象都存在彼此的引用,即使其它对这两个对象的引用都被删除,但是这两个对象都还存在一个有效引用,这样不会符合回收条件,要让GC能够回收它们就需要打破这样的一个环,如果是多个对象也许就是拆散一个网,隔离出来不再需要的对象,从而让它满足被回收的条件,如以下代码,当到doSth()时i3就已经满足被回收的条件了。
public class Island {
Island i;
Island ii;
/**
* @author ZhangXiang
* @param args
* 2010-1-15
*/
public static void main(String[] args) {
Island i1 = new Island();
Island i2 = new Island();
Island i3 = new Island();
i1.i = i2;//i1引用i2
i2.i = i3;//i2引用i3
i3.i = i1;//i3引用i1
i1.ii = i3;//i1又引用i3
i2.ii = i1;//i2又引用i1
i3.ii = i2;//i3引用i2
//隔离出i3,使它满足被回收的条件
i2.i= null;
i1.ii = null;
//doSth();
}
}
java GC虽然是自动执行的,但是了解它的原理,知道它是怎么执行的,有时候在实际开发中对我们的帮助还是蛮大的,这也是平常找工作面试、笔试常考的知识点,学习了好多次,总是隔段时间就要忘记不少,记下来分享下,也起到提醒自己的作用吧,当是笔记咯。
一般都只是看看别人写的博,自己很少写,有时看看自己写的文章,发现差距还是很大,其实写博也是记录自己成长的一种方式吧,学习中,还请大家多多指教。