成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

江義旺:滴滴出行安卓端 finalize time out 的解決方案

Betta / 870人閱讀

摘要:此前滴滴出行安卓端曾長期受此的影響,每天有一些用戶會(huì)因此遇到,經(jīng)過深度分析,最終找到有效解決方案。方法內(nèi)盡量減少耗時(shí)以及線程同步時(shí)間。減少高優(yōu)先級(jí)線程的創(chuàng)建和使用,降低高優(yōu)先級(jí)線程的使用率。

出品 | 滴滴技術(shù)

作者 | 江義旺


前言:隨著安卓 APP 規(guī)模越來越大,代碼越來越多,各種疑難雜癥問題也隨之出現(xiàn)。比較常見的一個(gè)問題就是 GC finalize() 方法出現(xiàn) java.util.concurrent.TimeoutException,這類問題難查難解,困擾了很多開發(fā)者。那么這類問題是怎么出現(xiàn)的呢?有什么解決辦法呢?這篇文章為將探索 finalize() timeout 的原因和解決方案,分享我們的踩坑經(jīng)驗(yàn),希望對(duì)遇到此類問題的開發(fā)者有所幫助。

在一些大型安卓 APP 中,經(jīng)常會(huì)遇到一個(gè)奇怪的 BUG:ava.util.concurrent.TimeoutException
其表現(xiàn)為對(duì)象的 finalize() 方法超時(shí),如 android.content.res.AssetManager.finalize() timed out after 10 seconds 。
此前滴滴出行安卓端曾長期受此 BUG 的影響,每天有一些用戶會(huì)因此遇到 Crash,經(jīng)過深度分析,最終找到有效解決方案。這篇文章將對(duì)這個(gè) BUG 的來龍去脈以及我們的解決方案進(jìn)行分析。

▍問題詳情

finalize() TimeoutException 發(fā)生在很多類中,典型的 Crash 堆棧如:


這類 Crash 都是發(fā)生在 java.lang.Daemons$FinalizerDaemon.doFinalize 方法中,直接原因是對(duì)象的 finalize() 方法執(zhí)行超時(shí)。系統(tǒng)版本從 Android 4.x 版本到 8.1 版本都有分布,低版本分布較多,出錯(cuò)的類有系統(tǒng)的類,也有我們自己的類。由于該問題在 4.x 版本中最具有代表性,下面我們將基于 AOSP 4.4 源碼進(jìn)行分析:

▍源碼分析

首先從 DaemonsFinalizerDaemon 的由來開始分析,Daemons 開始于 Zygote 進(jìn)程:Zygote 創(chuàng)建新進(jìn)程后,通過 ZygoteHooks 類調(diào)用了 Daemons 類的 start() 方法,在 start() 方法中啟動(dòng)了 FinalizerDaemon,FinalizerWatchdogDaemon 等關(guān)聯(lián)的守護(hù)線程。


Daemons 類主要處理 GC 相關(guān)操作,start() 方法調(diào)用時(shí)啟動(dòng)了 5 個(gè)守護(hù)線程,其中有 2 個(gè)守護(hù)線程和這個(gè) BUG 具有直接的關(guān)系。

▍FinalizerDaemon 析構(gòu)守護(hù)線程

對(duì)于重寫了成員函數(shù)finalize()的類,在對(duì)象創(chuàng)建時(shí)會(huì)新建一個(gè) FinalizerReference 對(duì)象,這個(gè)對(duì)象封裝了原對(duì)象。當(dāng)原對(duì)象沒有被其他對(duì)象引用時(shí),這個(gè)對(duì)象不會(huì)被 GC 馬上清除掉,而是被放入 FinalizerReference 的鏈表中。FinalizerDaemon 線程循環(huán)取出鏈表里面的對(duì)象,執(zhí)行它們的 finalize() 方法,并且清除和對(duì)應(yīng) FinalizerReference對(duì)象引用關(guān)系,對(duì)應(yīng)的 FinalizerReference 對(duì)象在下次執(zhí)行 GC 時(shí)就會(huì)被清理掉。


▍FinalizerWatchdogDaemon 析構(gòu)監(jiān)護(hù)守護(hù)線程

析構(gòu)監(jiān)護(hù)守護(hù)線程用來監(jiān)控 FinalizerDaemon 線程的執(zhí)行,采用 Watchdog 計(jì)時(shí)器機(jī)制。當(dāng) FinalizerDaemon 線程開始執(zhí)行對(duì)象的 finalize() 方法時(shí),FinalizerWatchdogDaemon 線程會(huì)啟動(dòng)一個(gè)計(jì)時(shí)器,當(dāng)計(jì)時(shí)器時(shí)間到了之后,檢測 FinalizerDaemon 中是否還有正在執(zhí)行 finalize() 的對(duì)象。檢測到有對(duì)象存在后就視為 finalize() 方法執(zhí)行超時(shí),就會(huì)產(chǎn)生 TimeoutException 異常。


由源碼可以看出,該 Crash 是在 FinalizerWatchdogDaemon 的線程中創(chuàng)建了一個(gè)TimeoutException 傳給 Thread 類的 defaultUncaughtExceptionHandler 處理造成的。由于異常中填充了 FinalizerDaemon 的堆棧,之所以堆棧中沒有出現(xiàn)和 FinalizerWatchdogDaemon 相關(guān)的類。

▍原因分析

finalize()導(dǎo)致的 TimeoutException Crash 非常普遍,很多 APP 都面臨著這個(gè)問題。使用 finalize() TimeoutException 為關(guān)鍵詞在搜索引擎或者 Stack Overflow 上能搜到非常多的反饋和提問,技術(shù)網(wǎng)站上對(duì)于這個(gè)問題的原因分析大概有兩種:

▍對(duì)象 finalize() 方法耗時(shí)較長

當(dāng) finalize() 方法中有耗時(shí)操作時(shí),可能會(huì)出現(xiàn)方法執(zhí)行超時(shí)。耗時(shí)操作一般有兩種情況,一是方法內(nèi)部確實(shí)有比較耗時(shí)的操作,比如 IO 操作,線程休眠等。另外有種線程同步耗時(shí)的情況也需要注意:有的對(duì)象在執(zhí)行 finalize() 方法時(shí)需要線程同步操作,如果長時(shí)間拿不到鎖,可能會(huì)導(dǎo)致超時(shí),如 android.content.res.AssetManager$AssetInputStream 類:


AssetManager 的內(nèi)部類 AssetInputStream 在執(zhí)行 finalize() 方法時(shí)調(diào)用 close() 方法時(shí)需要拿到外部類 AssetManager 對(duì)象鎖, 而在 AssetManager 類中幾乎所有的方法運(yùn)行時(shí)都需要拿到同樣的鎖,如果 AssetManager 連續(xù)加載了大量資源或者加載資源是耗時(shí)較長,就有可能導(dǎo)致內(nèi)部類對(duì)象 AssetInputStream 在執(zhí)行finalize() 時(shí)長時(shí)間拿不到鎖而導(dǎo)致方法執(zhí)行超時(shí)。


▍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í)間比較長,就會(huì)出現(xiàn) TimeoutException。

詳情請(qǐng)見∞

確實(shí)這兩個(gè)原因能夠?qū)е?finalize() 方法超時(shí),但是從 Crash 的機(jī)型分布上看大部分是發(fā)生在系統(tǒng)類,另外在 5.0 以上版本也有大量出現(xiàn),因此我們認(rèn)為可能也有其他原因?qū)е麓祟悊栴}:

▍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í)間,FinalizerDaemon 線程只能在 CPU 空閑時(shí)運(yùn)行,這種情況也可能會(huì)導(dǎo)致超時(shí)情況的發(fā)生,(從 Android 8.0 版本開始,FinalizerDaemon 中守護(hù)線程優(yōu)先級(jí)已經(jīng)被提高,此類問題已經(jīng)大幅減少)

▍解決方案

當(dāng)問題出現(xiàn)后,我們應(yīng)該找到問題的根本原因,從根源上去解決。然而對(duì)于這個(gè)問題來說卻不太容易實(shí)現(xiàn),和其他問題不同,這類問題原因比較復(fù)雜,有系統(tǒng)原因,也有 APP 自身的原因,比較難以定位,也難以系統(tǒng)性解決。

▍理想措施

理論上我們可以做的措施有:

1. 減少對(duì) finalize() 方法的依賴,盡量不依靠 finalize() 方法釋放資源,手動(dòng)處理資源釋放邏輯。

2. 減少 finalizable 對(duì)象個(gè)數(shù),即減少有 finalize() 方法的對(duì)象創(chuàng)建,降低 finalizable 對(duì)象 GC 次數(shù)。

3.finalize() 方法內(nèi)盡量減少耗時(shí)以及線程同步時(shí)間。

4. 減少高優(yōu)先級(jí)線程的創(chuàng)建和使用,降低高優(yōu)先級(jí)線程的 CPU 使用率。

▍止損措施

理想情況下的措施,可以從根本上解決此類問題,但現(xiàn)實(shí)情況下卻不太容易完全做到,對(duì)一些大型APP來說更難以徹底解決。那么在解決問題的過程中,有沒有別的辦法能夠緩解或止損呢?總結(jié)了技術(shù)網(wǎng)站上現(xiàn)有的方案后,可以總結(jié)為以下幾種:

· 手動(dòng)修改 finalize() 方法超時(shí)時(shí)間

理論上我們可以做的措施有:

1. 減少對(duì) finalize() 方法的依賴,盡量不依靠 finalize() 方法釋放資源,手動(dòng)處理資源釋放邏輯。

2. 減少 finalizable 對(duì)象個(gè)數(shù),即減少有 finalize() 方法的對(duì)象創(chuàng)建,降低 finalizable 對(duì)象 GC 次數(shù)。

3.finalize() 方法內(nèi)盡量減少耗時(shí)以及線程同步時(shí)間。

4. 減少高優(yōu)先級(jí)線程的創(chuàng)建和使用,降低高優(yōu)先級(jí)線程的 CPU 使用率。

▍止損措施

理想情況下的措施,可以從根本上解決此類問題,但現(xiàn)實(shí)情況下卻不太容易完全做到,對(duì)一些大型APP來說更難以徹底解決。那么在解決問題的過程中,有沒有別的辦法能夠緩解或止損呢?總結(jié)了技術(shù)網(wǎng)站上現(xiàn)有的方案后,可以總結(jié)為以下幾種:

· 手動(dòng)修改 finalize() 方法超時(shí)時(shí)間


詳情請(qǐng)見∞

這種方案思路是有效的,但是這種方法卻是無效的。Daemons 類中 的 MAX_FINALIZE_NANOS 是個(gè) long 型的靜態(tài)常量,代碼中出現(xiàn)的 MAX_FINALIZE_NANOS 字段在編譯期就會(huì)被編譯器替換成常量,因此運(yùn)行期修改是不起作用的。MAX_FINALIZE_NANOS默認(rèn)值是 10s,國內(nèi)廠商常常會(huì)修改這個(gè)值,一般有 15s,30s,60s,120s,我們可以推測廠商修改這個(gè)值也是為了加大超時(shí)的闕值,從而緩解此類 Crash。

· 手動(dòng)停掉 FinalizerWatchdogDaemon 線程


詳情請(qǐng)見∞

這種方案利用反射 FinalizerWatchdogDaemonstop() 方法,以使 FinalizerWatchdogDaemon 計(jì)時(shí)器功能永遠(yuǎn)停止。當(dāng) finalize() 方法出現(xiàn)超時(shí), FinalizerWatchdogDaemon 因?yàn)橐呀?jīng)停止而不會(huì)拋出異常。這種方案也存在明顯的缺點(diǎn):

1. 在 Android 5.1 版本以下系統(tǒng)中,當(dāng) FinalizerDaemon 正在執(zhí)行對(duì)象的 finalize() 方法時(shí),調(diào)用 FinalizerWatchdogDaemonstop() 方法,將導(dǎo)致 run() 方法正常邏輯被打斷,錯(cuò)誤判斷為 finalize() 超時(shí),直接拋出 TimeoutException。

2. Android 9.0 版本開始限制 Private API 調(diào)用,不能再使用反射調(diào)用 Daemons 以及 FinalizerWatchdogDaemon 類方法。

▍終極方案

這些方案都是阻止 FinalizerWatchdogDaemon 的正常運(yùn)行,避免出現(xiàn) Crash,從原理上還是具有可行性的:finalize() 方法雖然超時(shí),但是當(dāng) CPU 資源充裕時(shí),FinalizerDaemon 線程還是可以獲得充足的 CPU 時(shí)間,從而獲得了繼續(xù)運(yùn)行的機(jī)會(huì),最大可能的延長了 APP 的存活時(shí)間。但是這些方案或多或少都是有缺陷的,那么有其他更好的辦法嗎?

What should we do");

我們的方案就是忽略這個(gè) Crash,那么怎么能夠忽略這個(gè) Crash 呢?首先我們梳理一下這個(gè) Crash 的出現(xiàn)過程:

1. FinalizerDaemon 執(zhí)行對(duì)象 finalize() 超時(shí)。

2. FinalizerWatchdogDaemon 檢測到超時(shí)后,構(gòu)造異常交給 Thread 的 defaultUncaughtExceptionHandler 調(diào)用 uncaughtException() 方法處理。

3. APP 停止運(yùn)行。

Thread 類的 defaultUncaughtExceptionHandler 我們很熟悉了,Java Crash 捕獲一般都是通過設(shè)置 Thread.setDefaultUncaughtExceptionHandler() 方法設(shè)置一個(gè)自定義的 UncaughtExceptionHandler ,處理異常后通過鏈?zhǔn)秸{(diào)用,最后交給系統(tǒng)默認(rèn)的 UncaughtExceptionHandler 去處理,在 Android 中默認(rèn)的 UncaughtExceptionHandler 邏輯如下:


從系統(tǒng)默認(rèn)的 UncaughtExceptionHandler 中可以看出,APP Crash 時(shí)彈出的停止運(yùn)行對(duì)話框以及退出進(jìn)程操作都是在這里處理中處理的,那么只要不讓這個(gè)代碼繼續(xù)執(zhí)行就可以阻止 APP 停止運(yùn)行了。基于這個(gè)思路可以將這個(gè)方案表示為如下的代碼:


· 可行性

這種方案在 FinalizerWatchdogDaemon 出現(xiàn) TimeoutException 時(shí)主動(dòng)忽略這個(gè)異常,阻斷 UncaughtExceptionHandler 鏈?zhǔn)秸{(diào)用,使系統(tǒng)默認(rèn)的 UncaughtExceptionHandler 不會(huì)被調(diào)用,APP 就不會(huì)停止運(yùn)行而繼續(xù)存活下去。由于這個(gè)過程用戶無感知,對(duì)用戶無明顯影響,可以最大限度的減少對(duì)用戶的影響。

· 優(yōu)點(diǎn)

1. 對(duì)系統(tǒng)侵入性小,不中斷 FinalizerWatchdogDaemon 的運(yùn)行。

2.Thread.setDefaultUncaughtExceptionHandler() 方法是公開方法,兼容性比較好,可以適配目前所有 Android 版本。

總結(jié)

不管什么樣的緩解措施,都是治標(biāo)不治本,沒有從根源上解決。對(duì)于這類問題來說,雖然人為阻止了 Crash,避免了 APP 停止,APP 能夠繼續(xù)運(yùn)行,但是 finalize() 超時(shí)還是客觀存在的,如果 finalize() 一直超時(shí)的狀況得不到緩解,將會(huì)導(dǎo)致 FinalizerDaemonFinalizerReference 隊(duì)列不斷增長,最終出現(xiàn) OOM 。因此還需要從一點(diǎn)一滴做起,優(yōu)化代碼結(jié)構(gòu),培養(yǎng)良好的代碼習(xí)慣,從而徹底解決這個(gè)問題。當(dāng)然 BUG 不斷,優(yōu)化不止,在解決問題的路上,緩解止損措施也是非常重要的手段。誰能說能抓老鼠的白貓不是好貓呢?




? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 江 義 旺
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 滴滴 | 業(yè)務(wù)平臺(tái)技術(shù)
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 資深軟件研發(fā)工程師

曾就職于奇虎360,長期從事移動(dòng)端研發(fā),2018年加入滴滴,專注于安卓移動(dòng)端性能優(yōu)化,架構(gòu)演進(jìn),新技術(shù)探索,開源項(xiàng)目DroidAssist 作者。



? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 看完江義旺同學(xué)的分享
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 你有怎樣的心得體會(huì)");? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 請(qǐng)?jiān)诹粞詤^(qū)告訴我們吧




文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/7375.html

相關(guān)文章

  • Flutter 仿滴滴出行App,android系統(tǒng)開發(fā)書籍

    摘要:目前本項(xiàng)目僅部分開源已開源內(nèi)容漢字轉(zhuǎn)拼音庫城市列表,索引懸停效果也許是目前最好用的工具類也許是目前最好用的屏幕工具類國際化常用工具類庫成都市詳細(xì)使用請(qǐng)參考倉庫說明。成都市存儲(chǔ)實(shí)體對(duì)象示例。成都市北京市詳細(xì)使用請(qǐng)參考倉庫說明。 地圖:采用高德地圖,僅簡單完成了部分功能,基礎(chǔ)地圖,地址檢索,逆地理編碼。 界面:仿...

    番茄西紅柿 評(píng)論0 收藏2637
  • 精華閱讀第 9 期 |滴滴出行 iOS 客戶架構(gòu)演進(jìn)之路

    摘要:架構(gòu)都是演變出來的,沒有最好的架構(gòu),只有最合適的架構(gòu)最近,滴滴出行平臺(tái)產(chǎn)品中心技術(shù)負(fù)責(zé)人李賢輝接受了的采訪,闡述了滴滴的客戶端架構(gòu)模式與演變過程。李賢輝也是移動(dòng)開發(fā)精英俱樂部中的一員,所以本期重點(diǎn)推薦了這篇文章。 「架構(gòu)都是演變出來的,沒有最好的架構(gòu),只有最合適的架構(gòu)!」最近,滴滴出行平臺(tái)產(chǎn)品中心 iOS 技術(shù)負(fù)責(zé)人李賢輝接受了 infoQ 的采訪,闡述了滴滴的 iOS 客戶端架構(gòu)模式...

    Eirunye 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<