示例类
FinalizeOverride对象初始化时有个1M的数组属性。
public class FinalizeOverride {
private byte[] bytes = new byte[1024 * 1024];
public byte[] getBytes() {
return bytes;
}
public void setBytes(byte[] bytes) {
this.bytes = bytes;
}
@Override
protected void finalize() throws Throwable {
System.out.println("finalize: FinalizeOverride");
super.finalize();
}
}
不重写finalize方法
不重写finalize方法,gc时,如果满足gc条件,对象直接被回收,这里做个测试说明,先把上面FinalizeOverride重写的finalize方法的注释了,测试代码及gc信息如下:
public static void main(String[] args) throws InterruptedException {
System.gc();
FinalizeOverride finalizeOverride1 = new FinalizeOverride();
FinalizeOverride finalizeOverride2 = new FinalizeOverride();
FinalizeOverride finalizeOverride3 = new FinalizeOverride();
finalizeOverride1=null;
finalizeOverride2=null;
finalizeOverride3=null;
System.gc();
Thread.sleep(Integer.MAX_VALUE);
}
在代码中第2次调用System.gc()时,minor gc时回收掉了4M多的内存,剩余96K,已经把3个FinalizeOveride实例回收掉了。
这个示例是做基准示例,大家看了心理有数。
重写finalize方法
把FinalizeOveride类的finalize方法的注释去掉,第1次gc的时候,GC检测到重写了finalize方法,只是把FinalizeOveride放进了一个引用队列,由一个Finalizer线程从这个队列弹出然后执行完finalize方法,解除了强引用关系之后 ,下次GC才会回收,关于重写了finalize方法的gc过程,这里不做展开说明。
因为System.gc(),首先会进行minor gc,然后触发major gc,所以在major gc的时候,会回收掉这3个实例,gc信息如下:
可以看到minor gc后,还有3M多的内存。
下面说下弱引用。
弱引用
Java的弱引用其实资料都很多,这里不多说了,弱引用的对象在GC的时候一定会被回收掉,这里进行个测试说明下:
先把FinalizeOverride的finalize方法注释了,测试代码及gc情况如下:
private static ReferenceQueue<FinalizeOverride> referenceQueue = new ReferenceQueue<FinalizeOverride>();
public static void main(String[] args) throws InterruptedException {
System.gc();
WeakReference<FinalizeOverride> weakReference1 = new WeakReference<FinalizeOverride>(new FinalizeOverride(), referenceQueue);
WeakReference<FinalizeOverride> weakReference2 = new WeakReference<FinalizeOverride>(new FinalizeOverride(), referenceQueue);
WeakReference<FinalizeOverride> weakReference3 = new WeakReference<FinalizeOverride>(new FinalizeOverride(), referenceQueue);
System.gc();
Thread.sleep(Integer.MAX_VALUE);
}
上面构造的时候使用了个引用队列,这里先不关心。
在minor gc的时候,已经回收掉了4M多的内存,把3个FinalizeOverride实例回收了,因为采用了Java的弱引用。
那如果这个类重写了finalize方法呢,看下面:
重写finalize方法在其它类中通过弱引用方式使用
FinalizeOverride重写finalzie方法的注释去掉,执行上面的测试代码,先看gc信息:
很明显,minor gc之后,还有3M多的内存未被回收掉。
其实,弱引用的对象在gc的时候应该被回收掉,但是如果重写了finalize方法,在第一次gc的时候,只是没有了强引用关系,满足gc条件,但是这个时候被放到引用队列(又被强引用了)等待处理及下次gc的时候有可能被gc掉(不是放到引用队列下次就能gc掉的,因为需要在解除强引用关系后才行,这也是处理不好容易内存泄露或者导致其它gc问题的原因)。
总结
如果一个类重写了finalize方法,又被其它类采用弱引用的方式使用,那么gc时,如我上面的测试代码,并不需要主动解除什么强引用关系,就满足了gc的条件,但是gc时依然要遵守重写finalize方法实例的gc处理过程。
下面顺便提下上面使用的引用队列,使用引用队列是gc后会放到上面构造时的引用队列中,通过这个可以知道这个对象已经被gc了,就别再瞎用这个对象了,也可以做些其它处理(如相关资源释放操作),最后记得把它从引用队列中清除,避免内存泄露。
如下,一个简单示例:
public class Main {
private static ReferenceQueue<FinalizeOverride> referenceQueue = new ReferenceQueue<FinalizeOverride>();
public static void main(String[] args) throws InterruptedException {
new Thread(new Runnable() {
public void run() {
while (true) {
try {
referenceQueue.remove();//阻塞,直到有可用的.
System.out.println("object moved.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
System.gc();
WeakReference<FinalizeOverride> weakReference1 = new WeakReference<FinalizeOverride>(new FinalizeOverride(), referenceQueue);
WeakReference<FinalizeOverride> weakReference2 = new WeakReference<FinalizeOverride>(new FinalizeOverride(), referenceQueue);
WeakReference<FinalizeOverride> weakReference3 = new WeakReference<FinalizeOverride>(new FinalizeOverride(), referenceQueue);
System.gc();
Thread.sleep(Integer.MAX_VALUE);
}
}
p.s. 其实最近项目中使用的第三方框架(比较流行的)出现了这种情况,线上系统内存老是出现各种问题,关键这个工具还重写的finalize方法存在同步操作,真无奈,害我各种定位,查看堆栈、线程信息及翻源码,最后,只想说,谨慎重写finalize方法。