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

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

最近项目的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 异常。

      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 
    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">runInternal</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token function">isRunning</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
            <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">sleepUntilNeeded</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
                <span class="token comment">// We have been interrupted, need to see if this daemon has been stopped.</span>
                <span class="token keyword">continue</span><span class="token punctuation">;</span>
            <span class="token punctuation">}</span>
            final Object finalizing <span class="token operator">=</span> <span class="token function">waitForFinalization</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token keyword">if</span> <span class="token punctuation">(</span>finalizing <span class="token operator">!=</span> <span class="token keyword">null</span> <span class="token operator">&amp;&amp;</span> <span class="token operator">!</span>VMRuntime<span class="token punctuation">.</span><span class="token function">getRuntime</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">isDebuggerActive</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
                <span class="token function">finalizerTimedOut</span><span class="token punctuation">(</span>finalizing<span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token keyword">break</span><span class="token punctuation">;</span>
            <span class="token punctuation">}</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span></pre><p>从源码可以看出,waitForFinalization返回不为空就会报这个错。再看一下finalizerTimedOut的代码</p><pre class="prism-token token  language-javascript">      <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">finalizerTimedOut</span><span class="token punctuation">(</span><span class="token parameter">Object object</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token comment">// The current object has exceeded the finalization deadline; abort!</span>
        String message <span class="token operator">=</span> object<span class="token punctuation">.</span><span class="token function">getClass</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">".finalize() timed out after "</span>
                <span class="token operator">+</span> <span class="token punctuation">(</span><span class="token constant">MAX_FINALIZE_NANOS</span> <span class="token operator">/</span> <span class="token constant">NANOS_PER_SECOND</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">" seconds"</span><span class="token punctuation">;</span>
        <span class="token operator">...</span><span class="token operator">...</span>

        <span class="token keyword">if</span> <span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">getUncaughtExceptionPreHandler</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token keyword">null</span> <span class="token operator">&amp;&amp;</span>
                Thread<span class="token punctuation">.</span><span class="token function">getDefaultUncaughtExceptionHandler</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
            <span class="token comment">// If we have no handler, log and exit.</span>
            System<span class="token punctuation">.</span><span class="token function">logE</span><span class="token punctuation">(</span>message<span class="token punctuation">,</span> syntheticException<span class="token punctuation">)</span><span class="token punctuation">;</span>
            System<span class="token punctuation">.</span><span class="token function">exit</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span></pre><h1 id="2.%E5%8E%9F%E5%9B%A0" name="2.%E5%8E%9F%E5%9B%A0">2.原因</h1><p>原因其实有很多,核心还是对象 finalize() 方法耗时较长。

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

3.解决方法

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

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的代码)

      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”;
        <span class="token keyword">if</span> <span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">getUncaughtExceptionPreHandler</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token keyword">null</span> <span class="token operator">&amp;&amp;</span>
                Thread<span class="token punctuation">.</span><span class="token function">getDefaultUncaughtExceptionHandler</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
            <span class="token comment">// If we have no handler, log and exit.</span>
            System<span class="token punctuation">.</span><span class="token function">logE</span><span class="token punctuation">(</span>message<span class="token punctuation">,</span> syntheticException<span class="token punctuation">)</span><span class="token punctuation">;</span>
            System<span class="token punctuation">.</span><span class="token function">exit</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span></pre><p>看一下getUncaughtExceptionPreHandler和getDefaultUncaughtExceptionHandler的默认实现(在在com.android.internal.os.RuntimeInit里面)</p><pre class="prism-token token  language-javascript">LoggingHandler loggingHandler <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">LoggingHandler</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

Thread.setUncaughtExceptionPreHandler(loggingHandler);
Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(loggingHandler));

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

      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里面只有getDefaultUncaughtExceptionHandlernull的判断,所以这段代码在19也能运行。不过区别在于这么设置以后19里面不会有FATAL EXCEPTION的log,而28会有,因为LoggingHandler没有覆盖掉)

5.释疑

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

        @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;
}
}
}
    <span class="token keyword">private</span> boolean <span class="token function">waitForFinalization</span><span class="token punctuation">(</span><span class="token parameter">Object object</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token function">sleepFor</span><span class="token punctuation">(</span>FinalizerDaemon<span class="token punctuation">.</span><span class="token constant">INSTANCE</span><span class="token punctuation">.</span>finalizingStartedNanos<span class="token punctuation">,</span> <span class="token constant">MAX_FINALIZE_NANOS</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">return</span> object <span class="token operator">!=</span> FinalizerDaemon<span class="token punctuation">.</span><span class="token constant">INSTANCE</span><span class="token punctuation">.</span>finalizingObject<span class="token punctuation">;</span>
    <span class="token punctuation">}</span></pre><p>如果stop发生在waitForObject里面会直接返回null,直接continue了,不会打断正常逻辑。如果发生在waitForFinalization里面呢?也就是sleepFor里面呢?这样就直接返回。假设如下的情况,(超时默认是10s)A的finalize需要2s,一进去1s就stop了,此时A并没有finalize结束。那么finalizingObject=A,object肯定是A,那么返回false,直接就报超时错误

再看一下28

        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

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这个错误通常表示在Android应用程序中,由于某种原因,一个线程被阻塞或被挂起了。这个错误的根本原因可能是应用程序正在执行一些非常消耗时间的操作,例如网络请求或数据库查询等等,这些操作超过了系统默认的10秒钟超时时间。 解决方法: 1. 检查应用程序中是否有长时间运行的代码块,例如网络请求和长时间的计算等等,如果有,请将它们放到单独的线程中运行,以避免主线程被阻塞。 2. 如果您已经在使用多线程,请确保您正确地使用了同步机制,例如synchronized块和锁,以避免线程间出现竞态条件。 3. 检查您的代码是否存在死锁或资源争用等情况。您可以使用工具来帮助您找到这些问题,例如Android Studio的Profiler和Trace工具。 4. 如果您无法通过以上方法解决问题,请尝试增加Binder GC Watcher的超时时间。您可以在应用程序的manifest文件中添加以下代码: ```xml <application android:name=".MyApplication" ... > <activity ... android:process=":my_process" > ... </activity> <receiver android:name=".MyReceiver" > <intent-filter> <action android:name="com.example.MY_ACTION" /> </intent-filter> </receiver> <provider android:name=".MyProvider" android:authorities="com.example.myprovider" > </provider> <service android:name=".MyService" /> <service android:name=".MyForegroundService" android:foregroundServiceType="dataSync|mediaPlayback|location" /> <receiver android:name=".MyJobSchedulerReceiver" /> </application> ``` 其中,Binder GC Watcher的超时时间可以通过在MyApplication类中重写finalize()方法来设置,例如: ```java public class MyApplication extends Application { @Override protected void finalize() throws Throwable { BinderInternal.disableBackgroundScheduling(true); super.finalize(); } } ``` 请注意,这种方法仅适用于Android 9.0及以上的版本。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值