前言
最近遇到一個(gè) 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)
乍一看沒有任何業(yè)務(wù)相關(guān)的代碼嘿悬,不過數(shù)量還挺多实柠,而且大多數(shù)是 OPPO 手機(jī)。
原因
看 Crash 堆棧善涨,猜測(cè)應(yīng)該是資源回收超時(shí)了窒盐,不過具體原因還不清楚。從搜索引擎了解到钢拧,這個(gè)問題主要原因有以下原因:
- 對(duì)象 finalize() 方法耗時(shí)較長(zhǎng)
當(dāng) finalize() 方法中有耗時(shí)操作時(shí)蟹漓,可能會(huì)出現(xiàn)方法執(zhí)行超時(shí)。耗時(shí)操作一般有兩種情況娶靡,一是方法內(nèi)部確實(shí)有比較耗時(shí)的操作牧牢,比如 IO 操作,線程休眠等姿锭。 - 5.0 版本以下機(jī)型 GC 過程中 CPU 休眠導(dǎo)致
有種觀點(diǎn)認(rèn)為系統(tǒng)可能會(huì)在執(zhí)行 finalize() 方法時(shí)進(jìn)入休眠塔鳍, 然后被喚醒恢復(fù)運(yùn)行后,會(huì)使用現(xiàn)在的時(shí)間戳和執(zhí)行 finalize() 之前的時(shí)間戳計(jì)算耗時(shí)呻此,如果休眠時(shí)間比較長(zhǎng)轮纫,就會(huì)出現(xiàn) TimeoutException。 - IO 負(fù)載過高
許多類的 finalize() 都需要釋放 IO 資源焚鲜,當(dāng) APP 打開的文件數(shù)目過多掌唾,或者在多進(jìn)程或多線程并發(fā)讀取磁盤的情況下,隨著并發(fā)數(shù)的增加忿磅,磁盤 IO 效率將大大下降糯彬,導(dǎo)致 finalize() 方法中的 IO 操作運(yùn)行緩慢導(dǎo)致超時(shí)。 - FinalizerDaemon 中線程優(yōu)先級(jí)過低
FinalizerDaemon 中運(yùn)行的線程是一個(gè)守護(hù)線程葱她,該線程優(yōu)先級(jí)一般為默認(rèn)級(jí)別 (nice=0)撩扒,其他高優(yōu)先級(jí)線程獲得了更多的 CPU 時(shí)間,在一些極端情況下高優(yōu)先級(jí)線程搶占了大部分 CPU 時(shí)間吨些,F(xiàn)inalizerDaemon 線程只能在 CPU 空閑時(shí)運(yùn)行搓谆,這種情況也可能會(huì)導(dǎo)致超時(shí)情況的發(fā)生。(從 Android 8.0 版本開始豪墅,F(xiàn)inalizerDaemon 中守護(hù)線程優(yōu)先級(jí)已經(jīng)被提高泉手,此類問題已經(jīng)大幅減少)
解決方案
根本的解決方案是:優(yōu)化代碼,資源及時(shí)釋放偶器,從根源上避免資源回收超時(shí)斩萌。
不過由于代碼量的問題,短期內(nèi)無法嘗試該方案屏轰,那么只有退而求其次术裸,尋求緩解方案。
從網(wǎng)上看到關(guān)于該問題的解決方案主要有以下兩個(gè):
- 手動(dòng)修改 finalize() 方法超時(shí)時(shí)間
- 手動(dòng)停掉 FinalizerWatchdogDaemon 線程
直接說結(jié)論亭枷,這兩種方法都是無效的袭艺,所以終極解決方案是
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)
}
}
}
外部調(diào)用 Thread.setDefaultUncaughtExceptionHandler(ExceptionHandler())
替換異常處理器。
既然無法解決叨粘,那么就直接忽略它猾编,避免對(duì)用戶造成影響。
更新
評(píng)論區(qū)有大牛提出了更完美的解決方案升敲,大家可以嘗試一下答倡。感謝 @wfwf