Android內存優化方向探討

本文轉自內存優化方向探討html

前言

最近複習對以往項目中和學習中一些內存優化的方法進行下探討和彙總. 雖然如今Android設備的內存愈來愈大,4GB內存手機已經至關普片了,可是隻有有效的控制好內存才能將你的應用性能發揮到極致.因此控制好內存如今依然是Android應用中一個重要的技術指標.android

開篇以前咱們先說說內存.內存已是咱們很熟悉的概念了,可是你能說出手機內存和電腦內存的區別嗎?手機內存之間有差別嗎?內存是否大就好?git

  • 什麼是手機內存?

從本質上看二者是相同的,電腦上用的是DDR內存,而移動設備上的是LPDDR內存.LPDDR的英文全稱爲Low Power Double Data Rate,LP即爲低功耗,專門用於移動式的電子產品。相較於PC領域的DDR4內存,LPDDR4形態的內存更省電.程序員

  • 手機內存的差別?

如今主流的運行內存分別有LPDDR三、LPDDR4以及LPDDR4X.github

從上圖能夠看出LPDDR4的性能要比LPDDR3高出一 倍,而LPDDR4X相比LPDDR4工做電壓更低,因此也比LPDDR4省電20%-40%。固然圖中的數據是標準數據,不一樣的生成 廠商會有一些低頻或者高頻的版本,性能方面高頻要好於低頻.

  • 手機內存是否大就好?

其實從上面一點咱們就能得出結論. 若是一個用8GB LPDDR4X的手機性能通常是優於 12GB LPDDR3手機的. 可是內存並非一個孤立的概念,它跟操做系統、應用生態這些因素都有關. 相信你們也對幾年前的IPhone6 1GB的運行內存就能吊打4-6GB的Android手機還印象深入.一樣的內存,AndroidQ系統性能就會比Android4.0好.封閉規範的IOS系統就會比開放的Android系統好.web

常見誤區

通常你們對手機內存都有一個比較粗略的概念,就是分堆和棧,但實際上仍是複雜得多. 算法

主要包括程序計數器PC、虛擬機棧、本地方法棧、Java堆、方法區、常量池等,這裏推薦你們能夠看下 詳解內存優化的前因後果一文,有詳細的介紹.

這裏我只說下兩個容易陷入的誤區數組

  • 對象只要小於內存空間便可加載?

我曾經遇到一個問題,在加載一個5MB的圖片到界面時忘記壓縮出現了OOM,但當我查看日誌時發現可用堆內存明明還大於5MB,爲何就OOM呢? 緩存


後面我知道了堆內存其實還分新生代、老年代、永久代,各會佔據必定空間如上圖, 日誌裏看到的了大於5MB實際上是新老內存共同的結果.當遇到超大對象 時,會直接將超大對象跳過新生代放置到老年代中.實際上老年代空間裏已經不足5MB空間了.就算老年代空間足夠若是GC是 CMS(每一個廠商實現略有不一樣,通常是混合的算法),那麼只會標記清理,並不會壓縮,因此內存會碎片化,同時可能出現浮游垃圾.即便老年代空間大於5MB,也會出現 沒有 連續空間供該對象使用.
PS:系統給每一個應用分配的空間是有限的.當內存達到必定的 閥值也會報OOM.
因此即便內存空間足夠也要注意,大資源的使用.

  • 那內存越少越好?

這也是很多人有的誤區之一,甚至有人當內存是洪水猛獸,用得越少越好,這樣容易優化過分產生其餘問題.如今手機的發展內存愈來愈多,若是隻有一個具體的數值,作硬指標實際上是沒有必要的.APP是否佔用過多的內存,跟設備、性能、和當時的狀況也有關.可讓高端設備使用更多的內存,作到針對設備性能的好壞使用不一樣的內存分配和回收策略.bash

內存優化方向

  • 內存分級優化

要作到針對設備進行優化處理,須要一個良好的架構支撐,架構須要支持如下幾點:

  1. 設備分級: 使用相似device-year-class的策略對設備分級,對於低端機用戶能夠關閉複雜的動畫,或者是某些功能;使用 565格式的圖片,使用更小的緩存內存等。在現實環境下,不是每一個用戶的設備都跟咱們的測試機同樣高端,在開發過程我 們要學會思考功能要不要對低端機開啓、在系統資源吃緊的時候能不能作降級

  2. 緩存管理:咱們須要有一套統一的緩存管理機制,能夠適當地使用內存;當"系統有難"時,也要責無旁貸地歸還.咱們能夠 使用OnTrimMemory回調,根據不一樣的狀態決定釋放多少內存.對於大項目來講,可能存在幾十上百個模塊,統一緩存管 理能夠更好地監控每一個模塊的緩存大小.

  3. 進程模型:一個空的進程也會佔用10MB的內存,而有些應用啓動就有十幾個進程,甚至有些應用已經從雙進程保活升級到 四進程保活,因此減小應用啓動的進程數、減小常駐進程、節制的保活,對低端機內存優化很是重要.

  4. 安裝包大小: 安裝包中的代碼、資源、圖片以及so庫的體積,跟它們佔用的內存有很大的關係.一個80MB的應用很難在 512MB內存的手機上流暢運行.這種狀況咱們須要考慮針對低端機用戶推出輕量版本,例如QQ音樂HD、快手極速版、今日頭條極速版都是這個思路.

  • Bitmap優化

圖片資源內存通常佔APP總內存很大一部分,因此作內存優化永遠沒法避開圖片內存這個"永恆主題". 即便把全部的Bitmap都放到Native內存,並不表明圖片內存問題就徹底解決了,這樣作只是提高了系統內存利用率,減小了 GC帶來的一些問題而已. 那咱們該如何優化圖片內存呢?我推薦如下兩種方法:

  1. 統一圖片庫

圖片內存優化的前提是收攏圖片的調用,這樣咱們能夠作總體的控制策略.例如低端機使用565格式、更加嚴格的縮放算法, 可使用Glide、Fresco或者採起自研均可以.並且須要進一步將全部Bitmap.createBitmap、BitmapFactory相關的接口也一併收攏.

  1. 統一監控

在統一圖片庫後就很是容易監控Bitmap的使用狀況了,這裏主要有兩點須要注意.

  • 大圖片監控: 咱們須要注意某張圖片內存佔用是否過大,例如⻓寬遠遠大於View甚至是屏幕的⻓寬。在開發過程當中,若是 檢測到不合規的圖片使用,應該當即彈出對話框提示圖片所在的Activity和堆棧,更快發現並解決問題.在灰度 和線上環境下能夠將異常信息上報到後臺,咱們能夠計算有多少比例的圖片會超過屏幕的大小,也就是圖片的"超寬率".
  • 分析圖片總內存: 經過收攏圖片使用,咱們還能夠統計應用全部圖片佔用的內存,這樣在線上就能夠按不一樣的系統、屏幕分辨 率等維度去分析圖片內存的佔用狀況.在OOM崩潰的時候,也能夠把圖片佔用的總內存、Top N圖片的內存都寫到崩潰日誌中,幫助咱們排查問題.

講完設備分級和Bitmap優化,咱們發現架構和監控須要兩手抓,一個好的架構能夠減小甚至避免咱們犯錯,而一個好的監控 能夠幫助咱們及時發現問題.

  • 內存泄漏

內存泄漏簡單理解就是沒有及時的回收不用的內存,排查和解決內存泄漏也是內存優化沒法避開的工做之一.
內存泄漏主要分兩種狀況,一種是同一個對象泄漏,還有一種狀況更加糟糕,就是每次都會泄漏新的對象,可能會出現幾百上 千個無用的對象.
不少內存泄漏都是框架設計不合理所致使,各類各樣的單例滿天⻜,MVC中Controller的生命週期遠遠大於View.優秀的框架 設計能夠減小甚至避免程序員犯錯,固然這不是一件容易的事情,因此咱們還須要對內存泄漏創建持續的監控.

1. Java內存泄漏: 創建相似LeakCanary自動化檢測方案,至少作到Activity和Fragment的泄漏檢測.在開發過程,咱們但願 出現泄漏時能夠彈出對話框,讓開發者更加容易去發現和解決問題.內存泄漏監控放到線上並不容易,咱們能夠對生成的 Hprof內存快照文件作一些優化,裁剪大部分圖片對應的byte數組減小文件大小。好比一個100MB的文件裁剪後通常只剩下 30MB左右,使用7zip壓縮最後小於10MB,增長了文件上傳的成功率

2. OOM監控: 美團有一個Android內存泄露自動化鏈路分析組件Probe,它在發生OOM的時候生成Hprof內存快照,而後經過 單獨進程對這個文件作進一步的分析.不過在線上使用這個工具⻛險仍是比較大,在崩潰的時候生成內存快照有可能會導 致二次崩潰,並且部分手機生成Hprof快照可能會耗時幾分鐘,這對用戶形成的體驗影響會比較大.另外,部分OOM是由於 虛擬內存不足致使,這塊須要具體問題具體分析.

3.Native內存泄漏監控: 在 WeMobileDev的一篇文章《微信Android終端內存優化實踐》中,微信也作了一些監控方案上面的嘗試.

4. 針對沒法重編so的狀況: 使用了PLT Hook攔截庫的內存分配函數,其中PLT Hook是Native Hook的一種方案.而後重定向到咱們本身的實現後記錄分配的內存地址、大小、來源so庫路徑等信息,按期掃描分配與釋放是否配 對,對於不配對的分配輸出咱們記錄的信息。

5. 針對可重編的so狀況: 經過GCC的"-finstrument-functions"參數給全部函數插樁,樁中模擬調用棧入棧出棧操做;經過ld 的「–wrap」參數攔截內存分配和釋放函數,重定向到咱們本身的實現後記錄分配的內存地址、大小、來源so以及插樁記錄的 調用棧此刻的內容,按期掃描分配與釋放是否配對,對於不配對的分配輸出咱們記錄的信息.

  • GC監控

在實驗室或者內部試用環境,咱們也能夠經過Debug.startAllocCounting來監控Java內存分配和GC的狀況,須要注意的是這個 選項對性能有必定的影響,雖然目前還可使用,但已經被Android標記爲deprecated. 經過監控,咱們能夠拿到內存分配的次數和大小,以及GC發起次數等信息.

long allocCount = Debug.getGlobalAllocCount(); 
long allocSize = Debug.getGlobalAllocSize();
long gcCount = Debug.getGlobalGcInvocationCount();
複製代碼

上面的這些信息彷佛不太容易定位問題,在Android 6.0以後系統能夠拿到更加精準的GC信息.

//運行的GC次數 
Debug.getRuntimeStat("art.gc.gc-count");
//GC使用的總耗時,單位是毫秒
Debug.getRuntimeStat("art.gc.gc-time");
//阻塞式GC的次數 
Debug.getRuntimeStat("art.gc.blocking-gc-count");
//阻塞式GC的總耗時 
Debug.getRuntimeStat("art.gc.blocking-gc-time");
複製代碼

須要特別注意阻塞式GC的次數和耗時,由於它會暫停應用線程,可能致使應用發生卡頓。咱們也能夠更加細粒度地分應用場景統計,如登錄、啓動、主業務等關鍵場景.

常見優化方式

常見的優化方式 如handler、webview、內存抖動、匿名內存類等等網上還有不少就不一一介紹了.建議參考Android內存優化全解析探索 Android 內存優化方法

總結

在進行內存的優化前,咱們首先搞清楚要優化的程度?內存產生了什麼異常和卡頓? 只有在明確了APP現狀和優化目標後,咱們才能去進行下一步的操做.在探討了內存優化的思路時,針對不一樣的設備、設備不一樣的狀況,咱們但願能夠給用戶不一樣的體驗.這裏我主要講到了關於 Bitmap內存優化和內存泄漏排查、監控的一些方法.但願對你們有所幫助.

參考文章

Manage Your App's Memory

爲何內存使用不多的時候也GC

一文解決內存屏障

leakcanary

相關文章
相關標籤/搜索