垃圾回收器,通常就叫它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虽然是自动执行的,但是了解它的原理,知道它是怎么执行的,有时候在实际开发中对我们的帮助还是蛮大的,这也是平常找工作面试、笔试常考的知识点,学习了好多次,总是隔段时间就要忘记不少,记下来分享下,也起到提醒自己的作用吧,当是笔记咯。
一般都只是看看别人写的博,自己很少写,有时看看自己写的文章,发现差距还是很大,其实写博也是记录自己成长的一种方式吧,学习中,还请大家多多指教。