借助HotSpot SA浅析finalize机制

13 篇文章 1 订阅
7 篇文章 0 订阅

相信Java程序猿都对Object#finalize不陌生,那么你有没有想过,这个方法,是在什么时候,如何被调用的?今天我们借助HotSpot SA中的一个工具,FinalizerInfo,来回答如何被调用这个问题(什么时候被调用的问题就先欠着好了 ^_^)。

关于finalize相关实现的官方文档比较少,我翻了好久才找到这篇Troubleshooting Guide for HotSpot VM中的这一小节有相关的描述 ,

One other potential source of OutOfMemoryError arises with applications that make excessive use of finalizers. If a class has a finalize method, then objects of that type do not have their space reclaimed at garbage collection time. Instead, after garbage collection the objects are queued for finalization, which occurs at a later time. In the Sun implementation, finalizers are executed by a daemon thread that services the finalization queue. If the finalizer thread cannot keep up with the finalization queue, then the Java heap could fill up and OutOfMemoryError would be thrown.

也就是说,在对象被回收之前,需要执行finalize方法,而finalize方法的执行又是需要排着队由某个线程来一个个消费的。下面我们通过会阻塞住的finalize方法来验证看看,

    private static class Foo {
        @Override
        protected void finalize() throws Throwable {
            System.out.println("finalize#" + this);
            super.finalize();
            System.in.read(); // 这个finalize方法将会卡住
        }
    }

    private static class Bar {
        @Override
        protected void finalize() throws Throwable {
            System.out.println("finalize#" + this);
            super.finalize();
            System.in.read(); // 这个finalize方法也会卡住
        }
    }

    public static void main(String[] args) throws Exception{
    
        foo();
        bar();

        System.gc();
        Thread.sleep(2000);
        System.in.read();
    }

    private static Foo foo() {
        return new Foo();
    }

    private static Bar bar() {
        return new Bar();
    }

如果上述没错,那么Foo跟Bar只要其中一个的finalize方法执行了,另一个必定得不到执行,因为单个队列,有一个卡住了那么其后续的必然也无法被消费了。
事实确实如此,输出只有一行finalize#me.kisimple.just4fun.Main$Bar@571688,所以Foo必定是在等待着被finalize。这时候FinalizerInfo就派上用场了,用它我们可以观察VM中有哪些正在等待被finalize的对象,

# java sun.jvm.hotspot.tools.FinalizerInfo 5960
Attaching to process ID 5960, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 24.65-b04
Number of objects pending for finalization: 33

Count	Class description
-------------------------------------------------------
17	java.util.zip.ZipFile$ZipFileInputStream
11	java.util.zip.ZipFile$ZipFileInflaterInputStream
4	java.io.FileInputStream
1	me.kisimple.just4fun.Main$Foo

妥妥的我们看到了1 me.kisimple.just4fun.Main$Foo,验证了文档中的描述。

FinalizerThread

更进一步,我们可以冲进FinalizerInfo的源码看看,

        /*
         * The implementation here has a dependency on the implementation of
         * java.lang.ref.Finalizer. If the Finalizer implementation changes it's
         * possible this method will require changes too. We looked into using
         * ObjectReader to deserialize the objects from the target VM but as
         * there aren't any public methods to traverse the queue it means using
         * reflection which will also tie us to the implementation.
         *
         * The assumption here is that Finalizer.queue is the ReferenceQueue
         * with the objects awaiting finalization. The ReferenceQueue queueLength
         * is the number of objects in the queue, and 'head' is the head of the
         * queue.
         */

注释就已经告诉我们,存放等待finalize的对象的队列就是在java.lang.ref.Finalizer.queue。然后去看看Finalizer的源码,可以看到消费这个queue的线程,也就是Finalizer.FinalizerThread线程,

        public void run() {
            if (running)
                return;

            // Finalizer thread starts before System.initializeSystemClass
            // is called.  Wait until JavaLangAccess is available
            while (!VM.isBooted()) {
                // delay until VM completes initialization
                try {
                    VM.awaitBooted();
                } catch (InterruptedException x) {
                    // ignore and continue
                }
            }
            final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
            running = true;
            for (;;) {
                try {
                    Finalizer f = (Finalizer)queue.remove();
                    f.runFinalizer(jla);
                } catch (InterruptedException x) {
                    // ignore and continue
                }
            }
        }
    private void runFinalizer(JavaLangAccess jla) {
        synchronized (this) {
            if (hasBeenFinalized()) return;
            remove();
        }
        try {
            Object finalizee = this.get();
            if (finalizee != null && !(finalizee instanceof java.lang.Enum)) {

                /// 最后由JavaLangAccess来真正执行Object#finalize了
                jla.invokeFinalize(finalizee);

                /* Clear stack slot containing this variable, to decrease
                   the chances of false retention with a conservative GC */
                finalizee = null;
            }
        } catch (Throwable x) { }
        super.clear();
    }

还有另一个办法,我们可以在FinalizerThread打断点进行调试,这样也是能验证我们的想法的。alright,今天就先到这吧 ^_^

参考资料

  • http://openjdk.java.net/jeps/132
  • http://docs.oracle.com/javase/7/docs/webnotes/tsg/TSG-VM/html/memleaks.html#gbyvh
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值