前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >finalize() timed out after 10 seconds的解决方案

finalize() timed out after 10 seconds的解决方案

作者头像
提莫队长
发布2020-06-02 15:32:08
3.8K0
发布2020-06-02 15:32:08
举报
文章被收录于专栏:刘晓杰

最近项目的bugly报了一个错finalize() timed out after 10 seconds。最初遇到这个问题,本人一脸懵逼。没写过这个方法怎么会在这里面报错的?查阅了网上的资料才发现,通常这个错误发生在 java.lang.Daemons$FinalizerDaemon.doFinalize的方法中,直接原因是对象的 finalize() 方法执行超时。接下来就有必要看一下Daemons的方法。

1.主要流程

Daemons 开始于 Zygote 进程:Zygote 创建新进程后,通过 ZygoteHooks 类调用了 Daemons 类的 start() 方法,在 start() 方法中启动了 FinalizerDaemon,FinalizerWatchdogDaemon 等关联的守护线程。

FinalizerDaemon 析构守护线程

对于重写了成员函数finalize()的类,在对象创建时会新建一个 FinalizerReference 对象,这个对象封装了原对象。当原对象没有被其他对象引用时,这个对象不会被 GC 马上清除掉,而是被放入 FinalizerReference 的链表中。FinalizerDaemon 线程循环取出链表里面的对象,执行它们的 finalize() 方法,并且清除和对应 FinalizerReference对象引用关系,对应的 FinalizerReference 对象在下次执行 GC 时就会被清理掉。

FinalizerWatchdogDaemon 析构监护守护线程

析构监护守护线程用来监控 FinalizerDaemon 线程的执行,采用 Watchdog 计时器机制。当 FinalizerDaemon 线程开始执行对象的 finalize() 方法时,FinalizerWatchdogDaemon 线程会启动一个计时器,当计时器时间到了之后,检测 FinalizerDaemon 中是否还有正在执行 finalize() 的对象。检测到有对象存在后就视为 finalize() 方法执行超时,就会产生 TimeoutException 异常。

代码语言:javascript
复制
      private Object waitForFinalization() {
            long startCount = FinalizerDaemon.INSTANCE.progressCounter.get();
            // Avoid remembering object being finalized, so as not to keep it alive.
            if (!sleepFor(MAX_FINALIZE_NANOS)) {
                // Don't report possibly spurious timeout if we are interrupted.
                return null;
            }
            if (getNeedToWork() && FinalizerDaemon.INSTANCE.progressCounter.get() == startCount) {
                Object finalizing = FinalizerDaemon.INSTANCE.finalizingObject;
                sleepFor(NANOS_PER_SECOND / 2);
                if (getNeedToWork()
                        && FinalizerDaemon.INSTANCE.progressCounter.get() == startCount) {
                    return finalizing;
                }
            }
            return null;
        }

        @Override 
        public void runInternal() {
            while (isRunning()) {
                if (!sleepUntilNeeded()) {
                    // We have been interrupted, need to see if this daemon has been stopped.
                    continue;
                }
                final Object finalizing = waitForFinalization();
                if (finalizing != null && !VMRuntime.getRuntime().isDebuggerActive()) {
                    finalizerTimedOut(finalizing);
                    break;
                }
            }
        }

从源码可以看出,waitForFinalization返回不为空就会报这个错。再看一下finalizerTimedOut的代码

代码语言:javascript
复制
      private static void finalizerTimedOut(Object object) {
            // The current object has exceeded the finalization deadline; abort!
            String message = object.getClass().getName() + ".finalize() timed out after "
                    + (MAX_FINALIZE_NANOS / NANOS_PER_SECOND) + " seconds";
            ......

            if (Thread.getUncaughtExceptionPreHandler() == null &&
                    Thread.getDefaultUncaughtExceptionHandler() == null) {
                // If we have no handler, log and exit.
                System.logE(message, syntheticException);
                System.exit(2);
            }
        }

2.原因

原因其实有很多,核心还是对象 finalize() 方法耗时较长。 比如方法内部确实有比较耗时的操作,比如 IO 操作,线程休眠等,再比如有的对象在执行 finalize() 方法时需要线程同步操作,如果长时间拿不到锁,可能会导致超时,也有可能是5.0 版本以下机型 GC 过程中 CPU 休眠导致

3.解决方法

(1)手动修改 finalize() 方法超时时间

代码语言:javascript
复制
try {
    Class<?> c = Class.forName(“java.lang.Daemons”);
    Field maxField = c.getDeclaredField(“MAX_FINALIZE_NANOS”);
    maxField.setAccessible(true);
    maxField.set(null, Long.MAX_VALUE);
} catch (Exception e) {
}

这种方案思路是有效的,但是这种方法却是无效的。Daemons 类中 的 MAX_FINALIZE_NANOS 是个 long 型的静态常量,代码中出现的 MAX_FINALIZE_NANOS 字段在编译期就会被编译器替换成常量,因此运行期修改是不起作用的。

(2)手动停掉 FinalizerWatchdogDaemon 线程(现在使用最多的)

这种方案利用反射 FinalizerWatchdogDaemon 的 stop() 方法,以使 FinalizerWatchdogDaemon 计时器功能永远停止。当 finalize() 方法出现超时, FinalizerWatchdogDaemon 因为已经停止而不会抛出异常。这种方案也存在明显的缺点:

  • 在 Android 5.1 版本以下系统中,当 FinalizerDaemon 正在执行对象的 finalize() 方法时,调用 FinalizerWatchdogDaemon 的 stop() 方法,将导致 run() 方法正常逻辑被打断,错误判断为 finalize() 超时,直接抛出 TimeoutException。(这个我后面会解释)
  • Android 9.0 版本开始限制 Private API 调用,不能再使用反射调用 Daemons 以及 FinalizerWatchdogDaemon 类方法。(本人测试过,至少在Mate20Pro上面还是可以的)

4.解决方案

先仔细分析finalizerTimedOut方法(这是android28的代码)

代码语言:javascript
复制
      private static void finalizerTimedOut(Object object) {
            // The current object has exceeded the finalization deadline; abort!
            String message = object.getClass().getName() + ".finalize() timed out after "
                    + (MAX_FINALIZE_NANOS / NANOS_PER_SECOND) + " seconds";
            ......

            if (Thread.getUncaughtExceptionPreHandler() == null &&
                    Thread.getDefaultUncaughtExceptionHandler() == null) {
                // If we have no handler, log and exit.
                System.logE(message, syntheticException);
                System.exit(2);
            }
        }

看一下getUncaughtExceptionPreHandler和getDefaultUncaughtExceptionHandler的默认实现(在在com.android.internal.os.RuntimeInit里面)

代码语言:javascript
复制
LoggingHandler loggingHandler = new LoggingHandler();
Thread.setUncaughtExceptionPreHandler(loggingHandler);
Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(loggingHandler));

loggingHandler 的具体实现就是FATAL EXCEPTION IN SYSTEM PROCESS这个Log。KillApplicationHandler明显就是退出程序的。所以可以在这里做文章,直接ignore

代码语言:javascript
复制
      final Thread.UncaughtExceptionHandler defaultUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
      Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                if (t.getName().equals("FinalizerWatchdogDaemon") && e instanceof TimeoutException) {
                    Log.e("ignore", "ignore");
                } else {
                    defaultUncaughtExceptionHandler.uncaughtException(t, e);
                }
            }
        });

(另外补充说一句,android19里面只有getDefaultUncaughtExceptionHandler==null的判断,所以这段代码在19也能运行。不过区别在于这么设置以后19里面不会有FATAL EXCEPTION的log,而28会有,因为LoggingHandler没有覆盖掉)

5.释疑

前面提到的利用反射 FinalizerWatchdogDaemon 的 stop() 方法的两个问题本人也是测试过。 先说第二种,也就是Android 9.0 版本开始限制 Private API 调用。我的华为Mate20Pro也是9.0的,但是依然可以使用,可能这个还和厂商有关吧 第一种:stop() 方法,将导致 run() 方法正常逻辑被打断,错误判断为 finalize() 超时。 这个就有必要看一下run的方法 先看19的

代码语言:javascript
复制
        @Override 
        public void run() {
            while (isRunning()) {
                Object object = waitForObject();
                if (object == null) {
                    // We have been interrupted, need to see if this daemon has been stopped.
                    continue;
                }
                boolean finalized = waitForFinalization(object);
                if (!finalized && !VMRuntime.getRuntime().isDebuggerActive()) {
                    finalizerTimedOut(object);
                    break;
                }
            }
        }

        private boolean waitForFinalization(Object object) {
            sleepFor(FinalizerDaemon.INSTANCE.finalizingStartedNanos, MAX_FINALIZE_NANOS);
            return object != FinalizerDaemon.INSTANCE.finalizingObject;
        }

如果stop发生在waitForObject里面会直接返回null,直接continue了,不会打断正常逻辑。如果发生在waitForFinalization里面呢?也就是sleepFor里面呢?这样就直接返回。假设如下的情况,(超时默认是10s)A的finalize需要2s,一进去1s就stop了,此时A并没有finalize结束。那么finalizingObject=A,object肯定是A,那么返回false,直接就报超时错误 再看一下28

代码语言:javascript
复制
        private Object waitForFinalization() {
            long startCount = FinalizerDaemon.INSTANCE.progressCounter.get();
            // Avoid remembering object being finalized, so as not to keep it alive.
            if (!sleepFor(MAX_FINALIZE_NANOS)) {
                // Don't report possibly spurious timeout if we are interrupted.
                return null;
            }
            if (getNeedToWork() && FinalizerDaemon.INSTANCE.progressCounter.get() == startCount) {
                Object finalizing = FinalizerDaemon.INSTANCE.finalizingObject;
                sleepFor(NANOS_PER_SECOND / 2);
                if (getNeedToWork()
                        && FinalizerDaemon.INSTANCE.progressCounter.get() == startCount) {
                    return finalizing;
                }
            }
            return null;
        }
        @Override 
        public void runInternal() {
            while (isRunning()) {
                if (!sleepUntilNeeded()) {
                    // We have been interrupted, need to see if this daemon has been stopped.
                    continue;
                }
                final Object finalizing = waitForFinalization();
                if (finalizing != null && !VMRuntime.getRuntime().isDebuggerActive()) {
                    finalizerTimedOut(finalizing);
                    break;
                }
            }
        }

(getNeedToWork() && FinalizerDaemon.INSTANCE.progressCounter.get() == startCount 这个判断逻辑看代码就可以知道和finalize有关,如果finalize结束,会通过lazySet来改变的) 如果stop在sleepFor(MAX_FINALIZE_NANOS)里面打断的话,会返回null,此时就走不到finalizerTimedOut 所以可以得出这样的结论,在android19手机上如果强制停掉FinalizerWatchdogDaemon,而这个stop是在waitForFinalization里面停掉的话同样也有可能出现这个错。而通常stop是在MainApplication里面的。那么报这个错只可能是一种情况:一开始启动app,但内存不够,某些对象执行了finalize方法,而此时正好碰上stop,就会有很高的几率发生(android28就不会再报这个错) 实验证明,mate20pro(9.0)确实不会再报这个错。周一的时候用公司的5.0的手机试一下强制stop会不会出问题

参考:https://segmentfault.com/a/1190000019373275

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.主要流程
    • FinalizerDaemon 析构守护线程
      • FinalizerWatchdogDaemon 析构监护守护线程
      • 2.原因
      • 3.解决方法
        • (1)手动修改 finalize() 方法超时时间
          • (2)手动停掉 FinalizerWatchdogDaemon 线程(现在使用最多的)
          • 4.解决方案
          • 5.释疑
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档