重写finalize方法的类实例,在其它类中被弱引用遇到gc的问题

  • Post author:
  • Post category:其他


示例类

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方法。



版权声明:本文为x763795151原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。