AssetManager.finalize() timed out 问题再分析及解决方案探究记录

本文深入分析了Android中AssetManager.finalize()方法执行超时引发的Crash问题,涉及FinalizerDaemon和FinalizerWatchdogDaemon线程的工作原理。通过源码解读和原因探讨,提出理想的解决措施以及实际应用中的止损方案,包括调整finalize方法超时时间、停用FinalizerWatchdogDaemon等,并最终提出一种在异常发生时忽略错误以防止APP崩溃的策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

首先,先向大家道歉:上一篇关于finalize() timed out的博客误导了大家,当问题出现后,我们应该找到问题的根本原因,从根源上去解决。然而对于这个问题来说却不太容易实现,和其他问题不同,这类问题原因比较复杂,有系统原因,也有 APP 自身的原因,比较难以定位,也难以系统性解决。探索的过程不会是一帆风顺的,我会努力提高自己对技术探索的严谨性,尽量避免此类错误,提高博客质量,和大家一起进步。

背景:18年6月份以后我们产品线的crash平台莫名出现了大量的TimeoutExceptions,crash的路径页面全部为空,log日志里面有多次的gc操作;一开始以为是网络请求方面的问题导致的,然后仔细排查了工程代码,也没有发现可以点。这个问题一直悬了好久,期间从问题根源角度尝试了各种办法,然后求证,最终找到了可行的方案,与大家分享。下面开始进入正题,问题分析:

 

问题详情

finalize() TimeoutException 发生在很多类中,典型的 Crash 堆栈如:

java.util.concurrent.TimeoutException:
android.content.res.AssetManager$AssetInputStream.finalize() timed out after 15 seconds
at android.content.res.AssetManager$AssetInputStream.close(AssetManager.java:559)
at android.content.res.AssetManager$AssetInputStream.finalize(AssetManager.java:592)
at java.lang.Daemons$FinalizerDaemon.doFinalize(Daemons.java:187)
at java.lang.Daemons$FinalizerDaemon.run(Daemons.java:170)
at java.lang.Thread.run(Thread.java:841)

 

这类 Crash 都是发生在 java.lang.Daemons$FinalizerDaemon.doFinalize 方法中,直接原因是对象的 finalize() 方法执行超时。系统版本从 Android 4.x 版本到 8.1 版本都有分布,低版本分布较多,出错的类有系统的类,也有我们自己的类。由于该问题在 4.x 版本中最具有代表性,下面我们将基于 AOSP 4.4 源码进行分析:

源码分析

首先从 Daemons 和 FinalizerDaemon 的由来开始分析,Daemons 开始于 Zygote 进程:Zygote 创建新进程后,通过 ZygoteHooks 类调用了 Daemons 类的 start() 方法,在 start() 方法中启动了 FinalizerDaemonFinalizerWatchdogDaemon  等关联的守护线程。

public final class Daemons {
    ...
    private static final long MAX_FINALIZE_NANOS = 10L * NANOS_PER_SECOND;

    public static void start() {
        FinalizerDaemon.INSTANCE.start();
        FinalizerWatchdogDaemon.INSTANCE.start();
        ...
    }

    public static void stop() {
        FinalizerDaemon.INSTANCE.stop();
        FinalizerWatchdogDaemon.INSTANCE.stop();
        ...
    }
}

 

Daemons 类主要处理 GC 相关操作,start() 方法调用时启动了 5 个守护线程,其中有 2 个守护线程和这个 BUG 具有直接的关系。

FinalizerDaemon 析构守护线程

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

 

private static class FinalizerDaemon extends Daemon {
    ...
    @Override public void run() {
        while (isRunning()) {
            // Take a reference, blocking until one is ready or the thread should stop
            try {
                doFinali
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值