Android記憶體洩漏監控和優化技巧總結
前言
對於Android平臺的應用程式來說,記憶體優化一直是個熱門話題,與傳統PC應用程式不同,Android上的應用一旦出現各種異常時系統預設會以最嚴厲的“崩潰”方式反饋給使用者,如果處理不當,將嚴重影響使用者體驗。
叢所周之,移動裝置的軟硬體資源無法與傳統PC相提並論(至少目前是這樣),因而開發人員在編寫應用時,需要有更多技巧、更精深的技術來應對各種局面。這其中尤以記憶體OOM(記憶體溢位)等涉及記憶體洩漏這樣的問題最為常見。
本文著重總結降低應用記憶體佔用的技巧以及對應的解決方案。
先來談談記憶體洩漏的監控機制
記憶體洩露:簡單來說物件由於編碼錯誤或系統原因,仍然存在著對其直接或間接的引用,導致系統無法進行回收。記憶體洩露,容易留下邏輯隱患,同時增加了應用記憶體峰值與發生OOM的概率。它屬於bug issue,是我們一定要修改的。下面是造成記憶體洩露的一些常見原因,但是如何建立一套發現記憶體洩露、解決記憶體洩露的閉環方案,才是我們工作的重點。
1監控方案
Square的開源庫leakcanry是一個非常不錯的選擇,它通過弱引用方式偵查Activity或物件的生命週期,若發現記憶體洩露自動dump Hprof檔案,通過HAHA庫得到洩露的最短路徑,最後通過notification展示。
記憶體洩露判斷與處理的流程如下圖 ,各自執行的程式空間(主程式通過idlehandler,HAHA分析使用的是單獨的程式):
微信在leakcanry推出之前已經有了自己的記憶體洩露監控體系,與leakcanry大致有以下的區別:
事實上,通過對leakcanry做簡單的定製,我們就可以實現以下一個記憶體洩露監控閉環。
2記憶體洩露後的挽救措施
Activity洩漏會導致該Activity引用到的Bitmap、DrawingCache等無法釋放,對記憶體造成大的壓力,挽救措施是指對於已洩漏Activity,嘗試回收其持有的資源,洩漏的僅僅是一個Activity空殼,從而降低對記憶體的壓力。
做法也非常簡單,在Activity onDestory時候從view的rootview開始,遞迴釋放所有子view涉及的圖片,背景,DrawingCache,監聽器等等資源,讓Activity成為一個不佔資源的空殼,洩露了也不會導致圖片資源被持有。
總的來說,我們不是隻懂得一些記憶體洩露解決方法就可以,更重要的是通過日常測試與監控,得到記憶體洩露檢測與修改的一整套閉環體系。
如何降低執行記憶體的佔用
1Android系統何時會發生OOM?
2按照慣例:優化Bitmap佔用的記憶體效果最為明顯
說到記憶體,bitmap必然是這裡的大頭。對於bitmap記憶體佔用,想說的有以下幾點:
一個好的imageLoader,可以將2.X、4.X或5.X對圖片載入的處理對使用者隱藏,同時也可以將自適應大小、質量等放於框架中。
3記憶體佔用情況實時監測
對於系統函式onLowMemory等函式是針對整個系統而已的,對於本程式來說,其dalvik記憶體距離OOM的差值並沒有體現,也沒有回撥函式供我們及時釋放記憶體。假若能有那麼一套機制,可以實時監控程式的堆記憶體使用率,達到設定值即關於通知相關模組進行記憶體釋放,這會大大的降低OOM。
- 實現原理:
4程式隔離
對於webview,相簿等,由於存在記憶體系統洩露或者佔用記憶體過多的問題,我們可以採用單獨的程式。微信當前也會把它們放在單獨的tools程式中。
5OOM錯誤資訊上報機制
當系統發生OOM的crash時,我們應當上傳更加詳細的記憶體相關資訊,方便我們定位當時記憶體的具體情況。其他例如使用large heap、inBitmap、SparseArray、Protobuf等不再一一細述,對程式碼採用優化--埋坑--優化--埋坑的方式並不推薦。我們應該著力於建立一套合理的框架與監控體系,能及時的發現諸如bitmap過大、畫素浪費、記憶體佔用過大、應用OOM等問題。
記憶體回收(GC)優化
1頻繁的GC會帶來何種影響
Java擁有GC的機制,不同的系統版本GC的實現可能有比較大的差異。但是無論哪種版本,大量的GC操作則會顯著佔用幀間隔時間(16ms)。如果在幀間隔時間裡面做了過多的GC操作,那麼自然其他類似計算,渲染等操作的可用時間就變得少了。
2GC的型別介紹
GC的型別有以下幾種,其中GC_FOR_ALLOC是同步方式進行,對應用幀率的影響最大。
- GC_FOR_ALLOC:
當堆記憶體不夠的時候容易被觸發,尤其是new一個物件的時候,很容易被觸發到,所以如果要加速啟動,可以提高dalvik.vm.heapstartsize的值,這樣在啟動過程中可以減少GC_FOR_ALLOC的次數。注意這個觸發是以同步的方式進行的。如果GC後仍然沒有空間,則堆進行擴張
- GC_EXPLICIT:
這個gc是被可以呼叫的,比如system.gc, 一般gc執行緒的優先順序比較低,所以這個垃圾回收的過程不一定會馬上觸發, 千萬不要認為呼叫了system.gc,記憶體的情況就能有所好轉
- GC_CONCURRENT:
當分配的物件大小超過384K時觸發,注意這是以非同步的方式進行回收的.如果發現大量反覆的Concurrent GC出現,說明系統中可能一直有大於384K的物件被分配,而這些往往是一些臨時物件,被反覆觸發了。給到我們的暗示是:物件的複用不夠。
- GC_EXTERNAL_ALLOC(在3.0系統之後被廢了):
Native層的記憶體分配失敗了,這類GC就會被觸發。如果GPU的紋理、bitmap、或者java.nio.ByteBuffers的使用沒有釋放,這種型別的GC往往會被頻繁觸發。
3記憶體抖動
Memory Churn記憶體抖動,記憶體抖動是因為在短時間內大量的物件被建立又馬上被釋放。瞬間產生大量的物件會嚴重佔用記憶體區域,當達到閥值,剩餘空間不夠的時候,會觸發GC從而導致剛產生的物件又很快被回收。即使每次分配的物件佔用了很少的記憶體,但是他們疊加在一起會增加Heap的壓力,從而觸發更多其他型別的GC。這個操作有可能會影響到幀率,並使得使用者感知到效能問題。
通過Memory Monitor,我們可以跟蹤整個app的記憶體變化情況。若短時間發生了多次記憶體的漲跌,這意味著很有可能發生了記憶體抖動。
4GC優化方案
通過Heap Viewer,我們可以檢視當前記憶體快照,便於對比分析哪些物件有可能發生了洩漏。更重要的工具是Allocation Tracker,追蹤記憶體物件的型別、堆疊、大小等。手Q有做一個統計工具,對Allocation Tracker的原始資料,按照(型別&堆疊)的組合(堆疊取棧頂的5層)統計某一種物件分配的大小、次數。同時按照次數、大小的排序,從多/大到少/小結合程式碼分析,並自頂向下的逐輪進行優化。
這樣,我們就可以快速知道發生記憶體抖動時,是因為哪些變數的建立造成頻繁GC。一般來說我們需要注意以下幾個方面:
- 字串拼接優化:
- 資源重用:
建立全球快取池,對頻繁申請、釋放的物件型別重用
- 減少不必要或不合理的物件:
例如在ondraw、getview中應減少物件申請,儘量重用。更多是一些邏輯上的東西,例如迴圈中不斷申請區域性變數等
- 選用合理的資料格式:
使用SparseArray, SparseBooleanArray, and LongSparseArray來代替Hashmap
寫在最後
我們並不能將記憶體優化中用到的所有技巧都一一說明,而且隨著Android版本的更替,可能很多方法都會變的過時。我在想更重要的是我們能持續的發現問題,精細化的監控,而不是一直處於"哪個有坑填哪裡的"的窘況。在這裡給大家的建議有:
率先考慮採用已有的工具;中國人喜歡重複造輪子,我們更推薦花精力去優化已有工具,為廣大碼農做貢獻。生活已不易,碼農何為為難碼農!
不拘泥於點,更重要在於如何建立合理的框架避免發生問題,或者是能及時的發現問題。
當前微信記憶體監控體系中也存在一些不盡人意的地方,在未來的日子裡也同樣需要努力去優化。
公眾號推薦:
相關文章
- Android效能優化篇之記憶體優化--記憶體洩漏Android優化記憶體
- Android常見記憶體洩漏總結Android記憶體
- Flutter 上的記憶體洩漏監控Flutter記憶體
- Android記憶體洩漏Android記憶體
- Android 記憶體洩漏Android記憶體
- 1.記憶體優化(一)記憶體洩漏記憶體優化
- [實戰] Flutter 上的記憶體洩漏監控Flutter記憶體
- Android之記憶體洩漏除錯學習與總結Android記憶體除錯
- Android記憶體洩漏檢測與修復技巧Android記憶體
- 分析記憶體洩漏和goroutine洩漏記憶體Go
- Android記憶體洩漏場景Android記憶體
- Android記憶體溢位、記憶體洩漏常見案例分析及最佳實踐總結Android記憶體溢位
- Android備忘錄《記憶體洩漏》Android記憶體
- Android中的記憶體洩漏模式Android記憶體模式
- Android中常見的記憶體洩漏Android記憶體
- 初步探究Android記憶體洩漏(1)Android記憶體
- 效能優化——記憶體洩漏(1)入門篇優化記憶體
- 【記憶體洩漏和記憶體溢位】JavaScript之深入淺出理解記憶體洩漏和記憶體溢位記憶體溢位JavaScript
- Android 分割槽和記憶體監控Android記憶體
- Android 輕鬆解決記憶體洩漏Android記憶體
- Android Handler機制之記憶體洩漏Android記憶體
- 記憶體洩漏與排查流程——安卓效能優化記憶體安卓優化
- iOS效能優化 - 工具Instruments之Leaks記憶體洩漏iOS優化記憶體
- JavaScript之記憶體溢位和記憶體洩漏JavaScript記憶體溢位
- js記憶體洩漏JS記憶體
- jvm 記憶體洩漏JVM記憶體
- Java記憶體洩漏Java記憶體
- Swift的ARC和記憶體洩漏Swift記憶體
- 記憶體洩漏-原因、避免和定位記憶體
- [Java基礎]記憶體洩漏和記憶體溢位Java記憶體溢位
- 記憶體洩漏的原因記憶體
- valgrind 記憶體洩漏分析記憶體
- 填坑總結:python記憶體洩漏排查小技巧Python記憶體
- java記憶體溢位和記憶體洩漏的區別Java記憶體溢位
- Android中使用Handler為何造成記憶體洩漏?Android記憶體
- Android Native 記憶體洩漏系統化解決方案Android記憶體
- android 關於記憶體優化的一些總結Android記憶體優化
- JVM——記憶體洩漏與記憶體溢位JVM記憶體溢位