垃圾回收器是在堆中的,那么回收的自然是new出来的对象,所以很有必要去了解对象在内存中的状态,主要有三种状态:
1.激活状态:当new出一个对象后,即可获得该对象的引用,就可以调用对象里的信息,这时这个对象就处于激活状态。
2.去活状态:当一个对象不再有变量引用时,它将进入去活状态。一般在活状态下系统会再次尝试其他引用指向该对象,若成功则可以返回到激活状态,否则进入死亡状态。
3.死亡状态:该对象与所有引用都断开了联系,而且在去活状态下尝试新的引用也失败,这将导致该对象永久性的死亡了,它将做为GC的重点监视对象,对其进行回收。
代码如下:
public class GCtest{ //创建若干个实例 public void create(){ for(int i=0;i<5;i++){ GCtest obj1=new GCtest(); System.gc(); } } public static void main(String[] args){ new GCtest().create(); } }
调用 java -verbose:gc GCtest,可以看到GC的执行效果如下,
[Full GC 255K->160K(5056K), 0.0041556 secs]
[Full GC 160K->160K(5056K), 0.0027023 secs]
[Full GC 160K->160K(5056K), 0.0025777 secs]
[Full GC 160K->160K(5056K), 0.0027353 secs]
[Full GC 160K->160K(5056K), 0.0026654 secs]
说明System.gc()确实强制的回收了垃圾对象,并且每次都回收成功。那么是不是这个System.gc()就这么有效呢?
稍稍改动上面的代码,将System.gc()放到循环体的外面,则只会进行一次回收,这就说明System.gc()并不一定执行,也并不一定有效。
事实上,GC只会回收死亡状态的对象,并且不是一定回收,而是在某些条件下才回收,如系统内存紧缺。调用System.gc()的真正含义,其实应该解释为:“现在有对象需要被回收了,你来看看是不得清理一下了?”。然而,GC会调用对象的finalize方法来尝试对其进行资源清理,如果该对象又被其他引用所指向,那就不会被回收,总之,回收的条件是由垃圾回收器来决定的,系统不能强行控制它。
疑问:为什么第一次回收的那个内存会大些呢?感觉这个类的加载初始化等操作有关,暂时不清楚。
如上面所说,GC会调用对象的finalize方法来尝试对其进行资源清理。finalize方法是Object基类上就有的,所以可以重写该方法,观察GC的执行。
代码如下:
public class GCtest{ public void finalize(){ System.out.println("finalized..."); } public static void main(String[] args){ GCtest obj= new GCtest(); obj=new GCtest(); System.gc(); } }
代码输入如下:
[Full GC 255K->160K(5056K), 0.0042078 secs]
finalized...
上述代码让obj指向另一个新的对象,那么第一行的new GCtest()在堆中就处于无"人"引用的状态。然后通知GC去回收,GC在回收时也调用了finalize方法。不过得注意,虽然这段代码确实表明一个对象已经无指向了,应该去回收了,但是还是必须得加上System.gc()这句,如果去掉这句不会执行回收。毕竟GC回收是有条件的,不可能在系统内存很充分的时候为了一个很小的对象去频繁执行回收操作,那效率之低就可想而知了,这也就再次印证了“System.gc()只是通知GC去回收,至于是否真的回收有GC自己判断”。
上述代码体现的是finalize发现无法让对象返回到激活状态的例子,下面代码尝试让GC在回收之前,执行finalize时,可以为对象重新找到引用,从而返回激活状态。
public class GCtest{ public static GCtest nullObj=null; public String name; public void finalize(){ System.out.println("finalized..."); this.nullObj=this; } public void show(){ System.out.println(this.name+" is still alive"); } public static void main(String[] args){ new GCtest().name="fly"; System.gc(); try{ Thread.sleep(1000); } catch(java.lang.InterruptedException ex ){ } System.out.println("看看你还活着没?"); nullObj.show(); } }
输出如下:
[Full GC 255K->160K(5056K), 0.0041748 secs]
finalized...
看看你还活着没?
fly is still alive
上述代码在GCtest类中重写了基类的finalize方法,在其中将一个null对象重新赋值。main方法中,new GCtest()由于没有引用指向,所以这个匿名对象可能会被gc回收,调用System.gc()后,jvm首先调用重写后的finalize的方法,然后调用之前null对象的方法,此时null对象已经不为null了,而是指向之前那个new GCtest()对象了。
注意,从打印顺序可以看出,GC的线程是一条单独的线程。此外,如果去掉sleep的话,会报空指针异常的,这说明GC并不是立即执行finalize的。为了让finalize立刻执行,则可以去掉sleep,然后在Ssytem.gc()的后加一句"System.runFinalization()";即可。