Loading

一次内存分析引出关于finalize()的问题

一 .背景:

使用jmap命令dump下java堆栈文件,使用MAT进行分析,发现java.lang.ref.Finalizer对象非常多,且Leak Suspects显示占了很多内存,怀疑是否Memory Leak

image-20210716172312941

二. 查阅资料

java问题之1:Java的Finalizer引发的内存溢出

重写finalize方法引发的内存泄露

Java 将弃用 finalize() 方法?

java.lang.ref.Finalizer占用高内存

java.lang.ref.FinalizerReference引发的内存泄漏

句柄自动释放问题

由 Finalizer 和 SocksSocketImpl 引起的 Fullgc 问题盘点


三. finalize()是一个对象逃逸被回收的最后渠道

FileInputStreamSocksSocketImpl实现了Object的finalize()方法,以下称这些对象为Finalizable对象

public class CrashedFinalizable {
    public static void main(String[] args) throws ReflectiveOperationException {
        for (int i = 0; ; i++) {
            new CrashedFinalizable();
            // other code
        }
    }

    @Override
    protected void finalize() {
        System.out.print("");
    }
}

关于这里的实现 : 重写且方法内部有代码

Notice the finalize() method – it just prints an empty string to the console. If this method were completely empty, the JVM would treat the object as if it didn't have a finalizer. Therefore, we need to provide finalize() with an implementation, which does almost nothing in this case.

A Guide to the finalize Method in Java

对应JVM的垃圾回收机制,这些实现了finalize()方法的对象会被绑定到一个Finalizer对象Finalizer对象加入到java.lang.ref.Finalizer.ReferenceQueue队列中,此队列是个双向链表。因为有被引用, 此时GC是无法回收这些Finalizable对象的。但一旦Eden区太多,Survivor区放不下,则有可能直接进入老年代。

通过jstack可以看到有一个FinalizerThread线程,它用来处理队列中的对象,对队列中对象调用finalize()方法之后,Finalizable对象与Finalizer对象解除绑定,此时都没有被引用,最后两个一起等待被下一次GC回收掉。

image-20210716173949340

  • FinalizerThread的创建、启动
static {
    ThreadGroup tg = Thread.currentThread().getThreadGroup();
    for (ThreadGroup tgn = tg;
         tgn != null;
         tg = tgn, tgn = tg.getParent());
    Thread finalizer = new FinalizerThread(tg);
    finalizer.setPriority(Thread.MAX_PRIORITY - 2);
    finalizer.setDaemon(true);
    finalizer.start();
}
  • remove()弹出Finalizer对象。执行runFinalizer()
public void run() {
        // in case of recursive call to 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
            }
        }
    }
}
  • runFinalizer().**invokeFinalize()调用finalize()方法,把Finalizable对象的引用置空,Finalizable对象被抛弃,等待被GC回收的命运。
    private void runFinalizer(JavaLangAccess jla) {
        synchronized (this) {
            if (hasBeenFinalized()) return;
            remove();
        }
        try {
            Object finalizee = this.get();
            if (finalizee != null && !(finalizee instanceof java.lang.Enum)) {
                jla.invokeFinalize(finalizee);
				//把Finalizable对象的引用置空,Finalizable对象被抛弃
                /* 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线程比主线程优先级低,所以FinalizerThread线程的处理速度则赶不上对象生成速度

设想如果是通过finalize()去close(), close()的作用则是释放资源(socket, file, etc), 这些都需要FD,如果不断的积累FD可能会出现"Too many open files in system"


四.为什么JVM要设置这种机制?

The main purpose of a finalizer is, however, to release resources used by objects before they're removed from the memory. A finalizer can work as the primary mechanism for clean-up operations, or as a safety net when other methods fail.

A Guide to the finalize Method in Java

  • 主要是针对一些资源对象操作时,由于一些异常以致资源无法释放,从而起到兜底释放资源的作用

总结:

  • Finalizer机制的初衷是为了对资源兜底释放
  • 过度和错误使用则可能造成Memory Leak或者OOM,最好不要使用,不可控,你可能把握不住
  • baeldung这个网站确实不错

文章不足之处,还请各位技术大佬们指正

posted @ 2021-07-16 18:16  FynnWang  阅读(1577)  评论(0编辑  收藏  举报