案例:(本篇還以上篇案例為例子)
public class CommUtil {
private static CommUtil instance;
private Context context;
private CommUtil(Context context) {
this.context=context;
}
public static CommUtil getInstance(Context context){
if(null==instance){
instance=new CommUtil(context);
}
return instance;
}
}
複製程式碼
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
CommUtil instance = CommUtil.getInstance(this);
}
}
複製程式碼
(將Activity的例項被一個單例物件所持有,在旋轉螢幕的時候造成記憶體洩漏) 將專案執行起來 開啟Android Profiler 觀察Memory 並且試著多次旋轉螢幕就會發現記憶體一直在增高


Dump java Heap: 點選就會生成app執行記憶體快照.hprof檔案 然後將APP完全退出 重新啟動 開啟Android Monitor 點選Dump java Heap點選生成一份還沒操作前(旋轉螢幕)的記憶體快照hprof檔案(為以後作對比用) 現在就已經生成好了2份hprof檔案 一份是沒有旋轉過螢幕的 一份是旋轉過螢幕多次的 然後選中Android Studio 最左邊的Captures 進行將hprof檔案匯出(因為MAT不支援Android Studio生成的未經轉換的hprof檔案 在匯出過程中Android Studio會為我們轉換好)


MAT上場了
安裝 Memory Analyzer 外掛


匯入生成的兩個檔案

這個時候可能會報錯
Unknown HPROF Version (JAVA PROFILE 1.0.3) (java.io.IOException) 這是應為studio生成的.hprof檔案和eclipse支援的.hprof檔案格式問題
轉換格式: 進入SDK目錄輸入 hprof-conv inputfilepath outputfilepath之後從新匯入即可
先來看看MAT都有哪些功能:
Overview檢視 該檢視會首頁總結出當前這個Heap dump佔用了多大的記憶體,其中涉及的類有多少,物件有多少,類載入器,如果有沒有回收的物件,會有一個連線,可以直接參看(圖中的Unreachable Objects Histogram)。

histogram檢視: 列舉記憶體中物件存在的個數和大小 Dominator tree檢視:該檢視會以佔用總記憶體的百分比來列舉所有例項物件,注意這個地方是物件而不是類了,這個檢視是用來發現大記憶體物件的 Top Consumers: 該檢視會顯示可能的記憶體洩漏點 Duplicate Classes: 該檢視顯示重複的類等資訊 本次重點圍繞histogram檢視講解 其他3個檢視也只是給我提供一些可能會造成記憶體洩漏的資訊 點選開啟Histograms檢視

開啟下面的皮膚Navigation History 選中histogram右鍵add to Compare Basket 新增到比較容器中(2個hprof檔案都做以上同樣的操作 將histogram 新增到比較容器中)

這時比較容器中就有2份hprof檔案 一份未做任何操作 一份做了多次旋轉螢幕(注意:匯入的比較容器的順序很重要 不然後面的資料看起來讓人費解)然後點選Compare the results (紅色感嘆號)進行比較

通過比較後就會生成一個比較結果表ComPared Tables

但是這個表內容太多 如何快速過濾出我們與我們自己寫的專案內容有關的呢 接著在Class Name 輸入我們的專案包名

通上圖就明顯看出經過2個hprof比較 在我們操作了旋轉多次螢幕後MainActivity的例項增加了然後我們就去分析下該hprof檔案是什麼導致MainActivity記憶體洩漏了

在Class Name處可以根據輸入的類名去查詢對應的物件例項(比如我們輸入MainActivty):

Objects:例項個數 Shallow Heap:所佔記憶體大小 Retained Heap:釋放後能回收多少記憶體 通過上圖看到了MainActivtiy在旋轉螢幕後產生了2個例項 再去觀察MainActivtiy具體被哪些物件引用呢 滑鼠選中MainActivtiy 例項右鍵 選擇with incoming references:



看到MainActivty被引用的地方這麼多 而且一屏還顯示不完 我們又如何去判斷是哪個導致記憶體洩漏的呢 MAT還有一個功能 就是通過遍歷GC Root樹去將那些有可能被GC回收的例項 將他們去除(備註:在GC Root樹中能找到的物件絕對不存在有記憶體洩漏的例項 因為他們在執行時會被回收的嘛 只有找不到的那些才是)滑鼠右鍵:



看那些單詞就知道是什麼意思 (排除 軟引用 弱引用 虛引用) 最後我們就看到了Commutil的instance引用了MainActivty例項 而後旋轉螢幕的時候 系統又建立了一個 所以有2個引用著MainActivtiy的例項 造成的記憶體洩漏:
然後就去修改程式碼: public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
CommUtil instance = CommUtil.getInstance(getApplicationContext());
}
}
複製程式碼