国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

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

Betta / 867人閱讀

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

出品 | 滴滴技術(shù)

作者 | 江義旺


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

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

▍問題詳情

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


這類 Crash 都是發(fā)生在 java.lang.Daemons$FinalizerDaemon.doFinalize 方法中,直接原因是對象的 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() 方法中啟動了 FinalizerDaemonFinalizerWatchdogDaemon 等關(guān)聯(lián)的守護(hù)線程。


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

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

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


▍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í)行對象的 finalize() 方法時(shí),FinalizerWatchdogDaemon 線程會啟動一個(gè)計(jì)時(shí)器,當(dāng)計(jì)時(shí)器時(shí)間到了之后,檢測 FinalizerDaemon 中是否還有正在執(zhí)行 finalize() 的對象。檢測到有對象存在后就視為 finalize() 方法執(zhí)行超時(shí),就會產(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)站上對于這個(gè)問題的原因分析大概有兩種:

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

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


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


▍5.0 版本以下機(jī)型 GC 過程中 CPU 休眠導(dǎo)致


有種觀點(diǎn)認(rèn)為系統(tǒng)可能會在執(zhí)行 finalize() 方法時(shí)進(jìn)入休眠, 然后被喚醒恢復(fù)運(yùn)行后,會使用現(xiàn)在的時(shí)間戳和執(zhí)行 finalize() 之前的時(shí)間戳計(jì)算耗時(shí),如果休眠時(shí)間比較長,就會出現(xiàn) TimeoutException。

詳情請見∞

確實(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)先級過低

FinalizerDaemon 中運(yùn)行的線程是一個(gè)守護(hù)線程,該線程優(yōu)先級一般為默認(rèn)級別 (nice=0),其他高優(yōu)先級線程獲得了更多的 CPU 時(shí)間,在一些極端情況下高優(yōu)先級線程搶占了大部分 CPU 時(shí)間,FinalizerDaemon 線程只能在 CPU 空閑時(shí)運(yùn)行,這種情況也可能會導(dǎo)致超時(shí)情況的發(fā)生,(從 Android 8.0 版本開始,FinalizerDaemon 中守護(hù)線程優(yōu)先級已經(jīng)被提高,此類問題已經(jīng)大幅減少)

▍解決方案

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

▍理想措施

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

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

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

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

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

▍止損措施

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

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

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

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

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

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

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

▍止損措施

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

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


詳情請見∞

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

· 手動停掉 FinalizerWatchdogDaemon 線程


詳情請見∞

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

1. 在 Android 5.1 版本以下系統(tǒng)中,當(dāng) FinalizerDaemon 正在執(zhí)行對象的 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ī)會,最大可能的延長了 APP 的存活時(shí)間。但是這些方案或多或少都是有缺陷的,那么有其他更好的辦法嗎?

What should we do");

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

1. FinalizerDaemon 執(zhí)行對象 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)行對話框以及退出進(jìn)程操作都是在這里處理中處理的,那么只要不讓這個(gè)代碼繼續(xù)執(zhí)行就可以阻止 APP 停止運(yùn)行了。基于這個(gè)思路可以將這個(gè)方案表示為如下的代碼:


· 可行性

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

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

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

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

總結(jié)

不管什么樣的緩解措施,都是治標(biāo)不治本,沒有從根源上解決。對于這類問題來說,雖然人為阻止了 Crash,避免了 APP 停止,APP 能夠繼續(xù)運(yùn)行,但是 finalize() 超時(shí)還是客觀存在的,如果 finalize() 一直超時(shí)的狀況得不到緩解,將會導(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ù)平臺技術(shù)
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 資深軟件研發(fā)工程師

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



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




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

轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/7375.html

相關(guān)文章

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

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

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

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

    Eirunye 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<