转载

深入分析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_flag 和 RegisterFinalizersAtInit 进行判断,如果类重写了 finalize 方法,且方法体不为空,则调用 register_finalizer 函数,继续看 register_finalizer 函数的实现:

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

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

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

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

对象 O 和 Finalizer 类的静态变量 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 被设置时,会调用 ReferenceQueue 的 enqueue 方法把 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...