转载

深入分析Object.finalize方法的实现原理

简书占小狼

转载请注明原创出处,谢谢!

“物有本末,事有始终。知其先后,则近道矣”

finalize

如果类中重写了 finalize 方法,当该类对象被回收时, finalize 方法有可能会被触发,下面通过一个例子说明 finalize 方法对垃圾回收有什么影响。

public class FinalizeCase {

    private static Block holder = null;

    public static void main(String[] args) throws Exception {
        holder = new Block();
        holder = null;
        System.gc();
        //System.in.read();
    }

    static class Block {
        byte[] _200M = new byte[200*1024*1024];
    }
}

Block 类中声明一个占用内存200M的数组,是为了方便看出来gc之后是否回收了 Block 对象,执行完的gc日志如下:

深入分析Object.finalize方法的实现原理

从gc日志中可以看出来,执行完 System.gc() 之后, Block 对象被如期的回收了,如果在 Block 类中重写了 finalize 方法,会是一样的结果么?

static class Block {
    byte[] _200M = new byte[200*1024*1024];
    @Override
    protected void finalize() throws Throwable {
        System.out.println("invoke finalize");
    }
}

执行完成gc日志如下:

深入分析Object.finalize方法的实现原理

和之前的gc日志进行比较,发现 finalize 方法确实被触发了,但是 Block 对象还在内存中,并没有被回收,这是为什么?

下面对 finalize 方法的实现原理进行分析。

finalize实现原理

《 JVM源码分析之Java对象的创建过程 》一文中分析了Java对象创建的整个过程,代码实现如下:

深入分析Object.finalize方法的实现原理

对象的初始化过程会对 has_finalizer_flagRegisterFinalizersAtInit 进行判断,如果类重写了 finalize 方法,且方法体不为空,则调用 register_finalizer 函数,继续看 register_finalizer 函数的实现:

深入分析Object.finalize方法的实现原理

其中 Universe::finalizer_register_method() 缓存的是 jdkjava.lang.ref.Finalizer 类的 register 方法,实现如下:

深入分析Object.finalize方法的实现原理

在jvm中通过 JavaCalls::call 触发 register 方法,将新建的对象 O 封装成一个 Finalizer 对象,并通过 add 方法添加到 Finalizer 链表头。

对象 OFinalizer 类的静态变量 unfinalized 有联系,在发生GC时,会被判定为活跃对象,因此不会被回收

FinalizerThread线程

Finalizer 类的静态代码块中会创建一个 FinalizerThread 类型的守护线程,但是这个线程的优先级比较低,意味着在cpu吃紧的时候可能会抢占不到资源执行。

深入分析Object.finalize方法的实现原理

FinalizerThread 线程负责从 ReferenceQueue 队列中获取 Finalizer 对象,如果队列中没有元素,则通过 wait 方法将该线程挂起,等待被唤醒

深入分析Object.finalize方法的实现原理

如果返回了 Finalizer 对象,执行对象的 runFinalizer() 方法,其实可以发现:在 runFinalizer() 方法中主动捕获了异常,即使在执行 finalize 方法抛出异常时,也没有关系。

深入分析Object.finalize方法的实现原理

通过 hasBeenFinalized 方法判断该对象是否还在链表中,并将该 Finalizer 对象从链表中删除,这样下次gc时就可以把原对象给回收掉了,最后调用了native方法 invokeFinalizeMethod ,其中 invokeFinalizeMethod 方法最终会找到并执行对象的 finalize 方法。

深入分析Object.finalize方法的实现原理

ReferenceHandler线程

有个疑问:既然 FinalizerThread 线程是从 ReferenceQueue 队列中获取 Finalizer 对象,那么 Finalizer 对象是在什么情况下才会被插入到 ReferenceQueue 队列中?

Finalizer 的祖父类 Reference 中定义了 ReferenceHandler 线程,实现如下:

深入分析Object.finalize方法的实现原理

pending 被设置时,会调用 ReferenceQueueenqueue 方法把 Finalizer 对象插入到 ReferenceQueue 队列中,接着通过 notifyAll 方法唤醒 FinalizerThread 线程执行后续逻辑,实现如下:

深入分析Object.finalize方法的实现原理

pending字段什么时候会被设置?

在GC过程的引用处理阶段,通过 oopDesc::atomic_exchange_oop 方法把发现的引用列表设置在 pending 字段所在的地址

深入分析Object.finalize方法的实现原理

Finalizer导致的内存泄漏

平常使用的Socket通信, SocksSocketImpl 的父类重写了 finalize 方法

深入分析Object.finalize方法的实现原理

这么做主要是为了确保在用户忘记手动关闭 socket 连接的情况下,在该对象被回收时能够自动关闭 socket 来释放一些资源,但是在开发过程中,真的忘记手动调用了 close 方法,那么这些 socket 对象可能会因为 FinalizeThread 线程迟迟没有执行到这些对象的 finalize 方法,而导致一直占用某些资源,造成内存泄露。

我是占小狼,如果读完觉得有收获的话,欢迎点赞加关注

原文  http://www.jianshu.com/p/9d2788fffd5f
正文到此结束
Loading...