上篇說了一些性能優化的理論部分,主要是回顧一下,有了理論,小平同志又講了,實踐是檢驗真理的唯一標準,對於內存泄露的問題,現在通過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本身又不能被幹掉,所以就發生了內存泄露。
Memory Monitor是Android Monitors中的一種,Monitors主要包括四種,Memory Monitor ,CPU Monitor ,NetWork Monitor, GPU Monitor ,今天介紹的是Memory Monitor ,其他的Monitor,在後面也準備講。
-
Memory Monitor界面
- 圖中水平方向是時間軸,豎直方向是內存的分配情況
- 圖中深藍色的區域,表示當前正在使用中的內存總量,淺藍色或者淺灰色區域,表示空閒內存或者叫作未分配內存。
- 左上角工具欄三個圓圈按鈕依次代表
GC按鈕 ,可以手動GC,回收程序垃圾
內存快照(Dump Java Heap) ,點擊可以生成一個文件(包名+日期+“.hprof”),可以記錄摸一個時間點內,程序內存的情況
Allocation Traking ,點擊一次開始, 再次點擊結束,也可以可以生成一個文件。
回到我們的程序,多點擊幾次GC,看一下這個應用的內存使用情況。
可以看到現在已經分配的內存有19.68M,我把手機旋轉一下,在看。
可以看到現在的內存使用量是21.09M,還是一樣的界面,卻多了1.41M!!!這很關鍵。
接下來,我們找一下,哪裏發生了泄露。點擊Dump Java Heap,生成快照文件tool.test.memory.memoryleak_2016.11.13_21.38.hprof,Android Studio 自動彈出HPROF Viewer來分析它。
現在介紹一下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中的實例後,這個實例的引用樹
- A板塊左上角列名解釋
列名 | 解釋 |
---|---|
Class Name | 類名,Heap中的所有Class |
Total Count | 內存中該類這個對象總共的數量,有的在棧中,有的在堆中 |
Heap Count | 堆內存中這個類 對象的個數 |
Sizeof | 每個該實例佔用的內存大小 |
Shallow Size | 所有該類的實例佔用的內存大小 |
Retained Size | 所有該類對象被釋放掉,會釋放多少內存 |
- B板塊右上角上角列名解釋
列名 | 解釋 |
---|---|
Instance | 該類的實例 |
Depth | 深度, 從任一GC Root點到該實例的最短跳數 |
Dominating Size | 該實例可支配的內存大小 |
B板塊右上角有個"的按鈕, 點擊會進入HPROF Analyzer的hprof的分析界面:
"
在這個界面中可以直接把內存泄露可能的類找出來。
下面分析一下MainActivity的泄露情況
- 一個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查看方式
有兩種查看方式,默認是Group by Method方式
- Group by Method:用方法來分類我們的內存分配
- Group by Allocator:用內存分配器來分類我們的內存分配
從上圖可以看出,首先以線程對象分類,Size是內存大小,Count是分配了多少次內存,點擊一下線程就會查看每個線程裏所有分配內存的方法
-
Group by Method方式
OK,-Memory Monitor -
Group by Allocator方式
右鍵可以直接跳到源碼
- 扇形統計圖
點擊統計圖按鈕,會生成上圖,扇形統計圖是以圓心爲起點,最外層是其內存實際分配的對象,每一個同心圓可能被分割成多個部分,代表了其不同的子孫,每一個同心圓代表他的一個後代,每個分割的部分代表了某一帶人有多人,你雙擊某個同心圓中某個分割的部分,會變成以你點擊的那一代爲圓心再向外展開。
作者:LooperJing
鏈接:http://www.jianshu.com/p/ef9081050f5c
來源:簡書
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。