標籤:卡頓 道具 時間軸 分類 bitmap 記憶體回收行程 main key 案例
上篇說了一些效能最佳化的理論部分,主要是回顧一下,有了理論,小平同志又講了,實踐是檢驗真理的唯一標準,對於記憶體泄露的問題,現在通過Android Studio內建工具Memory Monitor 檢測出來。效能最佳化的重要性不需要在強調,但是要強調一下,我並不是一個老司機,嘿嘿!沒用過這個工具的,請睜大眼睛。如果你用過,那麼就不用在看這篇部落格了。
先看一段會發生記憶體泄露的代碼
public class UserManger { private static UserManger instance; private Context context; private UserManger(Context context) { this.context = context; } public static UserManger getInstance(Context context) { if (instance == null) { instance = new UserManger(context); } return instance; }}
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); UserManger userManger = UserManger.getInstance(this); }}
代碼很簡單,就是一個單利模式泄露的情境,我們現在的關心的不是代碼本身,而是如何將代碼裡面的記憶體泄露給找出來。但是對於上面的代碼發生記憶體泄露的原因還是有必要提一下。
上篇部落格說了,記憶體流失產生的原因是:當一個對象已經不需要再使用了,本該被回收時,而有另外一個正在使用的對象持有它的引用從而就導致,對象不能被回收。這種導致了本該被回收的對象不能被回收而停留在堆記憶體中,就產生了記憶體流失。
在上面的代碼中,發生泄露的不是UserManger,而是MainActivity,UserManger中有一個靜態成員instance,其生命週期和應用程式的生命週期一致,當退出應用時,才能被銷毀,但是當GC準備回收MainActivity時,結果呢MainActivity的對象(this)在被UserManger所引用,UserManger本身又不能被幹掉,所以就發生了記憶體泄露。
monitors.png
Memory Monitor是Android Monitors中的一種,Monitors主要包括四種,Memory Monitor ,CPU Monitor ,NetWork Monitor, GPU Monitor ,今天介紹的是Memory Monitor ,其他的Monitor,在後面也準備講。
Memory Monitor.png
- 圖中水平方向是時間軸,豎直方向是記憶體的分配情況
- 圖中深藍色的地區,表示當前正在使用中的記憶體總量,淺藍色或者淺灰色地區,表示空閑記憶體或者叫作未分配記憶體。
- 記憶體分析的工具列,從上向下一共4個按鈕,依次是:
終止檢測的開關,沒什麼實質性的作用
就是手動調用GC,我們在抓記憶體前,一定要手動點擊 Initiate GC按鈕手動觸發GC,這樣抓到的記憶體使用量情況就是不包括Unreachable對象的(Unreachable指的是可以被記憶體回收行程回收的對象,但是由於沒有GC發生,所以沒有釋放,這時抓的記憶體使用量中的Unreachable就是這些對象)
(Dump Java Heap)點擊可以產生一個檔案(包名+日期+“.hprof”),可以記錄摸一個時間點內,程式記憶體的情況。擷取hprof檔案(hprof檔案是我們使用MAT工具分析記憶體時使用的檔案),但這裡直接產生的檔案MAT還不能直接使用,需用轉換成標準的hprof檔案。可以使用AndroidStudio轉換或者用hprof-conv命令轉化,網上可以查到
開始分配追蹤,第一次點擊可以指定追蹤記憶體的開始位置,第二次點擊可以結束追蹤的位置。這樣我們截取了一段要分析的記憶體,等待幾秒鐘AndroidStudio會給我們開啟一個Allocation視圖(感覺和MAT工具差不多,不過MAT工具更加強大,我們也可以擷取hprof檔案,使用MAT來分析)
回到我們的程式,多點擊幾次GC,看一下這個應用的記憶體使用量情況。
記憶體使用量情況.jpg
可以看到現在已經分配的記憶體有19.68M,我把手機旋轉一下,在看。
旋轉後記憶體使用量情況.png
可以看到現在的記憶體使用量量是21.09M,還是一樣的介面,卻多了1.41M!!!這很關鍵。
接下來,我們找一下,哪裡發生了泄露。點擊Dump Java Heap,產生快照檔案tool.test.memory.memoryleak_2016.11.13_21.38.hprof,Android Studio 自動彈出HPROF Viewer來分析它。
快照檔案分析.png
現在介紹一下HPROF Viewer的用法
- HPROF Viewer查看方式
左上方兩個紅框,是可選列表, 分別是用來選擇Heap地區, 和Class View的展示方式的.
Heap類型分為:
App Heap -- 當前App使用的Heap
Image Heap -- 磁碟上當前App的記憶體映射拷貝
Zygote Heap -- Zygote進程Heap(每個App進程都是從Zygote孵化出來的, 這部分基本是framework中的通用的類的Heap)
Class List View -- 類列表方式
Package Tree View -- 根據包結構的樹狀顯示
我通常點擊App heap下面的Classs Name把Heap中所有類按照字母順序排序,然後按照字母順序尋找。
- HPROF Viewer主要分ABC三大板塊
板塊A:這個應用中所有類的名字
版塊B:左邊類的所有執行個體
板塊C:在選擇B中的執行個體後,這個執行個體的引用樹
| 列名 |
解釋 |
| Class Name |
類名,Heap中的所有Class |
| Total Count |
記憶體中該類這個對象總共的數量,有的在棧中,有的在堆中 |
| Heap Count |
堆記憶體中這個類 對象的個數 |
| Sizeof |
每個該執行個體佔用的記憶體大小 |
| Shallow Size |
所有該類的執行個體佔用的記憶體大小 |
| Retained Size |
所有該類對象被釋放掉,會釋放多少記憶體 |
| 列名 |
解釋 |
| Instance |
該類的執行個體 |
| Depth |
深度, 從任一GC Root點到該執行個體的最短跳數 |
| Dominating Size |
該執行個體可支配的記憶體大小 |
B板塊右上方有個"的按鈕, 點擊會進入HPROF Analyzer的hprof的分析介面:
Analyzer Tasks.png
點擊Analyzer Tasks右邊的綠色運行箭頭,Android Studio會自動的根據此hprof檔案分析有哪些類是有記憶體流失的,如所示:
下面分析一下MainActivity的泄露情況
MainActivity發生記憶體泄露.png在這個介面中可以直接把記憶體泄露可能的類找出來。
- 一個Activity應該只有一個執行個體,但是從A地區來看 total count的值為2,heap count的值也為2,說明有一個是多餘的。
- 在B地區中可以看見兩個MainActivity的執行個體,點擊一個看他的引用樹情況
- 在C地區中可以看到MainActivity的執行個體Context被UserManger的 instance引用了,引用深度為1.
- 在Analyzer Tasks 地區中,直接告訴你Leaked Activities,MainActivity包含其中
多方面的證據表明MainActivity發生了記憶體泄露
解決方案
public class UserManger { private static UserManger instance; private Context context; private UserManger(Context context) { this.context = context; } public static UserManger getInstance(Context context) { if (instance == null) { if(context!=null){ instance = new UserManger(context.getApplicationContext()); } } return instance; }}
不要用Activity的Context,因為Activity隨時可能被回收,我們用Application的Context,Application的Context的生命週期是整個應用,不回收也沒有關係。
Memory Monitor獲得記憶體的動態視圖,Heap Viewer顯示堆記憶體中儲存了什麼,可惜Heap Viewer不能顯示你的資料具體分配在代碼的何處,如果還不過癮,想知道具體是哪些代碼使用了記憶體,還有一個功能是Allocation Tracker,用來記憶體配置追蹤。在記憶體配置圖中點擊途中標紅的部分,啟動追蹤,再次點擊就是停止追蹤,隨後自動產生一個alloc結尾的檔案,這個檔案就記錄了這次追蹤到的所有資料,然後會在右上方開啟一個資料面板
Allocation Tracker啟動追蹤
Allocation Tracker啟動追蹤.png
,
Allocation Tracker查看方式
Allocation Tracker查看方式
有兩種查看方式,預設是Group by Method方式
- Group by Method:用方法來分類我們的記憶體配置
- Group by Allocator:用記憶體 Clerk來分類我們的記憶體配置
從可以看出,首先以線程對象分類,Size是記憶體大小,Count是分配了多少次記憶體,點擊一下線程就會查看每個線程裡所有分配記憶體的方法
Group by Method方式
每個線程裡所有分配記憶體的方法.png
OK,-Memory Monitor
Group by Allocator方式
EY%HY_B74%BUE22C6$G~CTP.png
右鍵可以直接跳到源碼
- 扇形統計圖
[email protected]_1`[email protected]%S%6.png
點擊統計圖按鈕,會產生,扇形統計圖是以圓心為起點,最外層是其記憶體實際分配的對象,每一個同心圓可能被分割成多個部分,代表了其不同的子孫,每一個同心圓代表他的一個後代,每個分割的部分代表了某一帶人有多人,你雙擊某個同心圓中某個分割的部分,會變成以你點擊的那一代為圓心再向外展開。
Memory Monitor可以發現的問題
Memory Monitor工具為監控工具,是一種發現型或者說監控性質的工具,比如醫生的四大技能[望聞問切],[望]是第一步。這裡的Memory Monitor就是一種[望]的工具,目前我主要用它來看下面幾個記憶體問題:
1.發現記憶體抖動的情境
2.發現大記憶體對象分配的情境
3.發現記憶體不斷增長的情境
4.確定卡頓問題是否因為執行了GC操作
案例分析
上面的第一區段標記顯示記憶體突然增加了7M,我們也能看的很清楚,所以這個點我們要去定位了一下問題在哪裡,是Bitmap還是什麼原因造成的,第二區段標記是記憶體抖動,很明顯在很短的時間了發生了多次的記憶體配置和釋放。而且在發生記憶體抖動的時候,也能感覺到App的卡頓,可以看出來是由於執行了GC操作造成的。
記憶體的不斷增加通過Memory monitor很容易看出來,藍色的曲線是一路高歌猛進的,一看便知。
Android記憶體最佳化1 記憶體偵查工具1 Memory Monitor檢測記憶體泄露