前言
最近遇到一个 Crash
java.util.concurrent.TimeoutException: android.content.res.AssetManager.finalize() timed out after 120 seconds
at android.content.res.AssetManager.destroy(Native Method)
at android.content.res.AssetManager.finalize(AssetManager.java:576)
at java.lang.Daemons$FinalizerDaemon.doFinalize(Daemons.java:217)
at java.lang.Daemons$FinalizerDaemon.run(Daemons.java:200)
at java.lang.Thread.run(Thread.java:818)
乍一看没有任何业务相关的代码,不过数量还挺多,而且大多数是 OPPO 手机。
原因
看 Crash 堆栈,猜测应该是资源回收超时了,不过具体原因还不清楚。从搜索引擎了解到,这个问题主要原因有以下原因:
- 对象 finalize() 方法耗时较长
当 finalize() 方法中有耗时操作时,可能会出现方法执行超时。耗时操作一般有两种情况,一是方法内部确实有比较耗时的操作,比如 IO 操作,线程休眠等。 - 5.0 版本以下机型 GC 过程中 CPU 休眠导致
有种观点认为系统可能会在执行 finalize() 方法时进入休眠, 然后被唤醒恢复运行后,会使用现在的时间戳和执行 finalize() 之前的时间戳计算耗时,如果休眠时间比较长,就会出现 TimeoutException。 - IO 负载过高
许多类的 finalize() 都需要释放 IO 资源,当 APP 打开的文件数目过多,或者在多进程或多线程并发读取磁盘的情况下,随着并发数的增加,磁盘 IO 效率将大大下降,导致 finalize() 方法中的 IO 操作运行缓慢导致超时。 - FinalizerDaemon 中线程优先级过低
FinalizerDaemon 中运行的线程是一个守护线程,该线程优先级一般为默认级别 (nice=0),其他高优先级线程获得了更多的 CPU 时间,在一些极端情况下高优先级线程抢占了大部分 CPU 时间,FinalizerDaemon 线程只能在 CPU 空闲时运行,这种情况也可能会导致超时情况的发生。(从 Android 8.0 版本开始,FinalizerDaemon 中守护线程优先级已经被提高,此类问题已经大幅减少)
解决方案
根本的解决方案是:优化代码,资源及时释放,从根源上避免资源回收超时。
不过由于代码量的问题,短期内无法尝试该方案,那么只有退而求其次,寻求缓解方案。
从网上看到关于该问题的解决方案主要有以下两个:
- 手动修改 finalize() 方法超时时间
- 手动停掉 FinalizerWatchdogDaemon 线程
直接说结论,这两种方法都是无效的,所以终极解决方案是
class ExceptionHandler : Thread.UncaughtExceptionHandler {
private val defaultExceptionHandler = Thread.getDefaultUncaughtExceptionHandler()
override fun uncaughtException(t: Thread?, e: Throwable?) {
if (t?.name == "FinalizerWatchdogDaemon" && e is TimeoutException) {
// ignore it
} else {
defaultExceptionHandler.uncaughtException(t, e)
}
}
}
外部调用 Thread.setDefaultUncaughtExceptionHandler(ExceptionHandler())
替换异常处理器。
既然无法解决,那么就直接忽略它,避免对用户造成影响。
更新
评论区有大牛提出了更完美的解决方案,大家可以尝试一下。感谢 @wfwf
网友评论