Android Memory(四) -- 問題定位&解決方案1
highlight: a11y-dark
前言
在工作這幾年,我一直深受記憶體問題的困擾,在和記憶體的不斷抗爭中,我逐漸積累了一些記憶體的知識,接來下來我會用幾篇文章簡單記錄一下這幾年的我學到的記憶體相關的經驗。另外,本系列文章不去過多的分析Linux底層程式碼,只是探討遇到記憶體問題時的解決方法論。 以下是全部文章的標題和連結: 1. Android Memory(一) -- 記憶體基礎知識 2. Android Memory(二) -- 應用記憶體佔用分析 3. Android Memory(三) -- 問題原因分析 4. Android Memory(四) -- 問題定位&解決方案1 5. Android Memory(五) -- 問題定位&解決方案2 6. Android Memory(六) -- 記憶體日常監控
前幾篇文章,我介紹了各種記憶體問題的異常情況,這一篇文章討論一下遇到各種問題怎麼去定位解決。本章討論的場景主要是通過常規手段,如日誌、堆疊、程式碼二分回溯、線上監控等無法明確定位出來問題時,我們怎麼去分析記憶體問題。在遇到問題時,我們還是優先通過日誌、堆疊、程式碼二分回溯和線上監控等去定位分析問題。
一、程序虛擬記憶體不足問題解決
目前來說,很多應用都已經出了64位的版本,但是不可避免的還會出現很多機型不支援64位。目前我們統計的結果發現32位的佔比還高達60%(當然我們主要是做的海外,國內應該相對會好一些)。所以如果您的應用有虛擬記憶體不足的問題,在目前階段,還是很有必要去做一下優化。虛擬記憶體問題我們主要分三步去解決:
1. 記憶體洩露問題解決
在任何時候,記憶體洩露問題都是最先要收斂的,這時候我們可以藉助一些線下和線上的手段去定位解決。記憶體洩露我放在下一篇文章中詳細去討論,在本篇文章中我們不過多去討論。
2. 不合理的記憶體佔用分析,特別是比較大的記憶體佔用
在分析此部分問題時,可以通過自動化測試和手動結合的方式去測試: - 自動化測試: 在做自動化測試時,可以隔一段時間(如半個小時)或者記憶體達到一定閾值時(比如vss達到3G時),解析adb shell dumpsys meminfo的結果,將記憶體佔用通過圖形繪製出來,同時去dump記憶體Hprof檔案和smaps檔案以及當前的頁面截圖。接下來就是去分析記憶體異常時的Hprof檔案和smaps檔案。
在分析的過程中,我們可能要重點關注一下Bitmap、WebView、媒體播放器Player等記憶體使用大戶的情況,同時也需要關注一下是否有其他的異常記憶體佔用。
如果應用的Native佔用記憶體過高,可以接入微信Matrix工具中的Native記憶體監控部分,Matrix能監控到C層通過Malloc分配的記憶體,並將其以檔案的形式把堆疊打印出來,此部分資料可以在儲存Hprof檔案和Smaps檔案時,一併匯出來,輔助去分析解決記憶體問題。
- 手動測試: 在很多時候,手動測試都是最快最有效的方式去定位記憶體問題的方法。我們可以使用android studio來觀察記憶體佔用情況,但是我更傾向於將記憶體資訊列印到應用上面(如下圖),通過不斷的點選業務的按鈕,進入和退出業務,來觀察應用的記憶體情況。在發生異常時將業務的Hprof檔案、smaps檔案、Matrxi記憶體堆疊匯出來進行分析。
如果應用比較小,出現了Native記憶體問題,也可以藉助於mallocDebug工具去定位分析。
此外,在問題定位的過程中,我們可以對業務的進入和退出進行埋點,將應用內各個業務流轉的時序圖打印出來,可以輔助分析問題。
3. 虛擬記憶體真的不夠用了
前兩步很多時候會耗費開發者大量的精力,當把記憶體洩露問題、不合理的分配問題等,能力範圍內的記憶體問題都解決了,但虛擬記憶體問題依然嚴重,此時您可以試試下邊兩個工具:
-
阿里巴巴出品的神器Patrons: 專門為了解決Android 8.0以上,32位記憶體不足而生的神器,思路比較清奇,並且大小隻有20k,很多應用接入都證明了此方案的有效性,具體可以參考github上的介紹文件。
-
微信團隊出品的Matrix: 適配了Patrons沒有支援的8.0以下手機,並且添加了其他的優化。接入反饋也是相當的不錯。
二、 Java堆記憶體不足
此部分問題解決起來相對比較簡單,主要關注以下兩個方面:
1. 記憶體洩露問題解決
下一篇會做比較詳細的分析,此處不再贅述。
2. 通過Hprof觀察不合理的記憶體佔用
在出現Java記憶體不足時,我們可以通過自動化測試或者手動測試,在記憶體異常時去Dump記憶體,儲存Hprof檔案,然後通過Android Studio和MAT去分析Hprof檔案,重點關注一下Bitmap的問題以及不合理的記憶體分配持有情況。分析出來問題去解決即可,此部分相對簡單,分析的文章也比較多,我們就不過多的討論。
三、FD數量超過系統限制
此類問題一般都是由於FD洩露導致。FD主要包含以下幾種型別:
FD型別 | 說明 | | ------------------------------------------ | :------------------------------------: | | socket | 檢查網路請求 | | anon_inode | 檢查HandlerThread 執行緒 Looper InputChannel | | /dev/ashmem | 檢查資料庫操作 | | /data/data/或/data/app/或/storage/emulate/0/ | 檢查對應的檔案是否開啟未關閉 |
當線上出現FD問題是,如果日誌、堆疊、程式碼二分等方法無法定位問題,同樣我們可以通過自動化和手動分析的方式去定位問題: - 自動化測試:同虛擬記憶體一樣,間歇去獲取應用FD的數目,記錄一下業務流轉的場景,以及業務進入和退出時FD的變化情況進行分析。
- 手動測試:同樣我們可以將FD數量在頁面頂部打印出來,然後手動去做業務路徑的操作,分析業務進入退出後FD的變化。
當定位到洩露的場景後,就可以具體問題具體分析了。
四、執行緒數超過系統限制
執行緒數過多不僅僅會報執行緒數超出系統限制的OOM,同樣執行緒數過多還會影響應用的Vss、Pss等。
執行緒數的觀察可以通過自動化測試和手動去分析: - 自動化測試:同虛擬記憶體一樣,間歇去獲取應用執行緒的數目,記錄一下業務流轉的場景,以及業務進入和退出時執行緒數的變化情況進行分析。
- 手動測試:同樣我們可以將執行緒數量在頁面頂部打印出來,然後手動去做業務路徑的操作,分析業務的進入退出後,執行緒數的變化。 此外,我們也可以藉助於Android Studio的CPU分析或者借用xHook去Hook執行緒建立去觀察應用內部的執行緒數量變化。
遇見執行緒數超標問題有以下幾點需要去做:
1. 收斂應用內部的執行緒建立
很多業務在使用執行緒時,都是直接通過new Thread、new HandlerThread或者new Executors來獲取執行緒的。並且有些時候建立的執行緒池core size還比較大,並且沒有設定銷燬時間。此時我們需要梳理應用內部的Thread、HandlerThread、執行緒池的建立,然後提供應用內部統一的執行緒池,把業務內部分散的執行緒都收斂到統一執行緒池上。
2. 收斂三方SDK的執行緒數
三方SDK大多數情況下都沒有提供執行緒池注入的介面,有些內部還會大量建立執行緒池,完全不考慮接入方的感受,這也是我比較不齒的地方。此類問題收斂起來也比較麻煩。吐槽歸吐槽,問題我們還得解決,我們將三方SDK分為程式碼可控和不可控兩種分別來看: - 程式碼可控SDK:此時對SDK程式碼改造,支援傳入執行緒池,如果接入方沒有傳入執行緒池,再用SDK內部的執行緒池。
- 程式碼不可控SDK:首先我們可以督促三方SDK提供傳入執行緒池的介面,最好是三方SDK改造。如果行不通,我們可以使用滴滴Booster的解決方案,在程式碼編譯期間通過Transform替換,將SDK內部的執行緒池、執行緒建立都替換成應用公用的執行緒池。此處有幾個注意點:
- 做好替換後的風險評估,會不會導致三方SDK出現問題;
- 做好白名單管控,避免替換後出現時序問題或者執行緒同步問題。
- 對三方SDK最好提供單獨的執行緒池,不要和內部使用的執行緒池混用,避免出現三方SDK把執行緒池跑滿或者卡死,從而導致業務不正常的情況。
五、手機實體記憶體不足
如果手機實體記憶體不足導致的OOM比較多,對其他應用我們暫時沒有辦法處理,我們能做的就是做好我們自己的記憶體使用管控。做好高中低端機的分類,然後對各等級裝置分別做不同的記憶體策略。對低記憶體手機的記憶體使用策略儘量保守,比如Freso解碼後圖片佔用記憶體的最大值、公用執行緒池最大的執行緒數等;對高記憶體機型,我們可以稍微奔放一些,從而儘可能減少記憶體的問題出現。最後再次宣告,記憶體並不是使用的越少越好,合理才是最優。
總結
本篇文章只是對應用內部出現記憶體問題以後提供一個分析和解決問題的思路,不過多去討論三方開源庫的原始碼。當然記憶體問題多種多樣,很多時候還是要具體問題具體分析。
參考文章:
http://juejin.cn/post/7058098018683191310