Java垃圾回收机制中,Object.finalize()方法超时执行的警告机制

背景

最近Android项目,越来越多手机上报 java.util.concurrent.TimeoutException异常。具体出错信息如下:

出错信息

com.android.internal.os.BinderInternal$GcWatcher.finalize() timed out after 10 seconds

出错函数堆栈

php 复制代码
1 java.lang.Object.wait(Native Method)

2 java.lang.Object.wait(Object.java:422)

3 java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:188)

4 java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:209)

5 java.lang.Daemons$FinalizerDaemon.runInternal(Daemons.java:232)

6 java.lang.Daemons$Daemon.run(Daemons.java:103)

7 java.lang.Thread.run(Thread.java:764)

小结

在执行某些对象的finalize方法进行特定资源回收时,需要在10s 内执行完毕,不然就会出现超时错误

垃圾回收机制中,finalize方法的作用

java.lang.Daemons类存储JVM能正常运行的后台线程。

  • HeapTaskDaemon 是 Android 系统中的一个守护线程,它负责监控和管理 Dalvik 虚拟机的堆内存
  • ReferenceQueueDaemon 是 Android 系统中的一个守护线程,它负责监控和管理 Java 对象的引用队列。
  • FinalizerDaemon 是 Android 系统中的一个守护线程,它负责执行 Java 对象的 finalize() 方法。
  • FinalizerWatchdogDaemon 是 Android 系统中的一个守护线程,它负责监控和管理 Java 对象的 finalize() 方法。

Object的finalize方法作用

在Java中,垃圾回收器负责回收不再使用的对象。在回收对象之前,垃圾回收器会调用对象的finalize()方法。如果对象没有重写finalize()方法,则该方法不会执行任何操作。如果对象重写了finalize()方法,则该方法会在垃圾回收器回收对象之前被调用。

需要注意的是,finalize()方法的使用应该谨慎。因为finalize()方法的执行时间是不确定的,可能会导致系统性能下降。另外,finalize()方法只会被调用一次,因此不能保证对象的资源一定会被释放。

Object.finalize方法的核心执行过程

FinalizerDaemon 类的运行流程如下:

  1. 在 Java 虚拟机启动时,FinalizerDaemon 线程会被启动。
  2. FinalizerDaemon 线程会创建一个 ReferenceQueue 对象,用于存放 finalizer 引用。
  3. FinalizerDaemon 线程会进入一个无限循环,等待 finalizer 引用被加入队列。
  4. 当一个 finalizer 引用被加入队列时,FinalizerDaemon 会通知FinalizerWatchdogDaemon去监控finalize方法的执行时间,并执行finalize方法。如果监控到方法执行超时,则发出警告。
  5. finalize 方法执行完成后,FinalizerDaemon会被阻塞等待finalizer引用加入队列。重复步骤 4-5

FinalizerDaemon代码逻辑

java 复制代码
private static class FinalizerDaemon extends Daemon {

    private Object finalizingObject = null;

    FinalizerDaemon() {
        super("FinalizerDaemon");
    }

    @Override public void runInternal() {
        int localProgressCounter = progressCounter.get();

        while (isRunning()) {
            try {
                FinalizerReference<?> finalizingReference = (FinalizerReference<?>)queue.poll();
                if (finalizingReference != null) {
                    finalizingObject = finalizingReference.get();
                    progressCounter.lazySet(++localProgressCounter);
                } else {
                    finalizingObject = null;
                    progressCounter.lazySet(++localProgressCounter);
                    FinalizerWatchdogDaemon.INSTANCE.goToSleep();
                    // 1.
                    // 阻塞线程,直到队列中获取到需要回收的引用
                    finalizingReference = (FinalizerReference<?>)queue.remove();
                    finalizingObject = finalizingReference.get();
                    progressCounter.set(++localProgressCounter);
                    // 2.
                    // 唤醒FinalizerWatchdogDaemon监控程序,监控finalize方法执行
                    FinalizerWatchdogDaemon.INSTANCE.wakeUp();
                }
                // 3.
                // 核心逻辑调用引用的finalize方法
                doFinalize(finalizingReference);
            } catch (InterruptedException ignored) {
            } catch (OutOfMemoryError ignored) {
            }
        }
    }

    @FindBugsSuppressWarnings("FI_EXPLICIT_INVOCATION")
    private void doFinalize(FinalizerReference<?> reference) {
        FinalizerReference.remove(reference);
        Object object = reference.get();
        reference.clear();
        try {
            // 4. 核心方法
            // 调用finalize()方法,回收对象
            object.finalize();
        } catch (Throwable ex) {
            System.logE("Uncaught exception thrown by finalizer", ex);
        } finally {
            finalizingObject = null;
        }
    }
}

Object.finalize方法的警告机制核心执行过程

FinalizerWatchdogDaemon核心执行流程如下

  1. 在Java虚拟机启动时,FinalizerWatchdogDaemon后台线程会被启动,并且进入睡眠状态,直到FinalizerWatchdogDaemon被唤醒
  2. 记录当前监控的初始时间,然后执行sleep函数,睡眠10s。
  3. 睡醒之后,判断finalize方法是否超时。
  4. 超时则发出超时错误警告,否则重复2-4的步骤。
java 复制代码
private static class FinalizerWatchdogDaemon extends Daemon {
    @UnsupportedAppUsage
    private static final FinalizerWatchdogDaemon INSTANCE = new FinalizerWatchdogDaemon();

    private boolean needToWork = true;  // Only accessed in synchronized methods.

    private long finalizerTimeoutMs = 0;  // Lazily initialized.

    FinalizerWatchdogDaemon() {
        super("FinalizerWatchdogDaemon");
    }

    @Override public void runInternal() {
        while (isRunning()) {
            if (!sleepUntilNeeded()) {
                continue;
            }
            // 1
            // 执行Sleep函数,去睡眠10s
            final Object finalizing = waitForFinalization();
            // 2
            // 判断finalizing结果是否为空。空则表示超时,并将警告发出
            if (finalizing != null && !VMRuntime.getRuntime().isDebuggerActive()) {
                finalizerTimedOut(finalizing);
                break;
            }
        }
    }

    private synchronized boolean sleepUntilNeeded() {
        while (!needToWork) {
            try {
                wait();
            } catch (InterruptedException e) {
                // Daemon.stop may have interrupted us.
                return false;
            } catch (OutOfMemoryError e) {
                return false;
            }
        }
        return true;
    }

    private synchronized void goToSleep() {
        needToWork = false;
    }

    private synchronized void wakeUp() {
        needToWork = true;
        notify();
    }

    private synchronized boolean getNeedToWork() {
        return needToWork;
    }

    private boolean sleepForMillis(long durationMillis) {
        long startMillis = System.currentTimeMillis();
        while (true) {
            long elapsedMillis = System.currentTimeMillis() - startMillis;
            long sleepMillis = durationMillis - elapsedMillis;
            if (sleepMillis <= 0) {
                return true;
            }
            try {
                Thread.sleep(sleepMillis);
            } catch (InterruptedException e) {
                if (!isRunning()) {
                    return false;
                }
            } catch (OutOfMemoryError ignored) {
                if (!isRunning()) {
                    return false;
                }
            }
        }
    }

    private Object waitForFinalization() {
        if (finalizerTimeoutMs == 0) {
            finalizerTimeoutMs = VMRuntime.getRuntime().getFinalizerTimeoutMs();
            MAX_FINALIZE_NANOS = NANOS_PER_MILLI * finalizerTimeoutMs;
        }
        long startCount = FinalizerDaemon.INSTANCE.progressCounter.get();
        if (!sleepForMillis(finalizerTimeoutMs)) {
            return null;
        }
        if (getNeedToWork() && FinalizerDaemon.INSTANCE.progressCounter.get() == startCount) {
            if (getNeedToWork()
                    && FinalizerDaemon.INSTANCE.progressCounter.get() == startCount) {
                return finalizing;
            }
        }
        return null;
    }

    private static void finalizerTimedOut(Object object) {
        Exception syntheticException = new TimeoutException(message);
        try {
            Os.kill(Os.getpid(), OsConstants.SIGQUIT);
            // Sleep a few seconds to let the stack traces print.
            Thread.sleep(5000);
        } catch (Exception e) {
            System.logE("failed to send SIGQUIT", e);
        } catch (OutOfMemoryError ignored) {
            // May occur while trying to allocate the exception.
        }
        if (Thread.getUncaughtExceptionPreHandler() == null &&
                Thread.getDefaultUncaughtExceptionHandler() == null) {
            // If we have no handler, log and exit.
            System.logE(message, syntheticException);
            System.exit(2);
        }

        Thread.currentThread().dispatchUncaughtException(syntheticException);
    }
}

警告机制的好处&缺点

好处

FinalizerDaemon 线程负责执行回收对象的finalize方法。 FinalizerWatchdogDaemon线程负责监控finalize方法执行是否超时。 两者异步执行,相互尽量不干扰,提高了程序的效率和程序的安全性。

缺点

如果因为系统休眠或者其它原因,导致整个JVM临时停止工作。当JVM恢复运行的时候,FinalizerWatchdogDaemon 线程有可能会报出警告错误。这种情况不多见,但目前项目中确实存在。可以参考这篇文章链接Android 经验:TimeoutException 问题分析与解决

相关推荐
坊钰24 分钟前
【Java 数据结构】移除链表元素
java·开发语言·数据结构·学习·链表
chenziang130 分钟前
leetcode hot100 LRU缓存
java·开发语言
会说法语的猪35 分钟前
springboot实现图片上传、下载功能
java·spring boot·后端
码农老起36 分钟前
IntelliJ IDEA 基本使用教程及Spring Boot项目搭建实战
java·ide·intellij-idea
m0_7482398340 分钟前
基于web的音乐网站(Java+SpringBoot+Mysql)
java·前端·spring boot
时雨h44 分钟前
RuoYi-ue前端分离版部署流程
java·开发语言·前端
麒麟而非淇淋1 小时前
Day13 苍穹外卖项目 工作台功能实现、Apache POI、导出数据到Excel表格
java
小爬虫程序猿1 小时前
利用Java爬虫获取速卖通(AliExpress)商品详情的详细指南
java·开发语言·爬虫
Java编程乐园1 小时前
Java中以某字符串开头且忽略大小写字母如何实现【正则表达式(Regex)】
java·正则表达式