如何找到項目中存在的內存泄露的這些地方呢荠商?
1.確定是否存在內存泄露
- Android Monitors的內存分析
最直觀的看內存增長情況,知道該動作是否發(fā)生內存泄露蜕企。動作發(fā)生之前: GC完后內存1.4M; 動作發(fā)生之后:GC完后內存1.6M
- 使用MAT內存分析工具
MAT分析heap的總內存占用大小來初步判斷是否存在泄露Heap視圖中有一個Type叫做data object劲厌,即數據對象,也就是我們的程序中大量存在的類類型的對象刽射。在data object一行中有一列是“Total Size”军拟,其值就是當前進程中所有Java數據對象的內存總量,一般情況下誓禁,這個值的大小決定了是否會有內存泄漏懈息。我們反復執(zhí)行某一個操作并同時執(zhí)行GC排除可以回收掉的內存,注意觀察data object的Total Size值摹恰,正常情況下Total Size值都會穩(wěn)定在一個有限的范圍內辫继,也就是說由于程序中的的代碼良好怒见,沒有造成對象不被垃圾回收的情況。
反之如果代碼中存在沒有釋放對象引用的情況姑宽,隨著操作次數的增多Total Size的值會越來越大遣耍。那么這里就已經初步判斷這個操作導致了內存泄露的情況。
2.先找懷疑對象(哪些對象屬于泄露的)
MAT對比操作前后的hprof來定位內存泄露是泄露了什么數據對象低千。(這樣做可以排除一些對象配阵,不用后面去查看所有被引用的對象是否是嫌疑)快速定位到操作前后所持有的對象哪些是增加了(GC后還是比之前多出來的對象就可能是泄露對象嫌疑犯)。
技巧:Histogram中還可以對對象進行Group示血,比如選擇Group By Package更方便查看自己Package中的對象信息棋傍。
3. MAT分析hprof來定位內存泄露的原因所在。
(哪個對象持有了上面懷疑出來的發(fā)生泄露的對象)
- 1 Dump出內存泄露“當時”的內存鏡像hprof难审,分析懷疑泄露的類瘫拣;
-
2 把上面2得出的這些嫌疑犯一個一個排查個遍。步驟:
2.1 進入Histogram告喊,過濾出某一個嫌疑對象類
2.2 然后分析持有此類對象引用的外部對象(在該類上面點擊右鍵List Objects--->with incoming references)
2.3 再過濾掉一些弱引用麸拄、軟引用、虛引用黔姜,因為它們遲早可以被GC干掉不屬于內存泄露 (在類上面點擊右鍵Merge Shortest Paths to GC Roots--->exclude all phantom/weak/soft etc.references)
2.4 逐個分析每個對象的GC路徑是否正常此時就要進入代碼分析此時這個對象的引用持有是否合理拢切,這就要考經驗和體力了!(比如上課的例子中:旋轉屏幕后MainActivity有兩個秆吵,肯定MainActivity發(fā)生泄露了.
那誰導致他泄露的呢淮椰?
原來是我們的CommonUtils類持有了旋轉之前的那個MainActivity他,那是否合理纳寂?結合邏輯判斷當然不合理主穗,由此找到內存泄露根源是CommonUtils類持有了該MainActivity實例造成的。
怎么解決毙芜?
罪魁禍首找到了忽媒,怎么解決應該不難了,不同情況解決辦法不一樣腋粥,要靠你的智慧了晦雨。)context.getapplictioncontext()可以嗎?
可以0濉金赦!只要讓CommonUtils類不直接只有MainActivity的實例就可以了。
4 判斷一個應用里面內存泄露避免得很好对嚼,怎么看?
當app退出的時候绳慎,這個進程里面所有的對象應該就都被回收了纵竖,尤其是很容易被泄露的(View漠烧,Activity)是否還內存當中。
可以讓app退出以后靡砌,查看系統(tǒng)該進程里面的所有的View已脓、Activity對象是否為0.
工具:使用AndroidStudio--AndroidMonitor--System Information--Memory Usage查看Objects里面的views和Activity的數量是否為0.
命令行模式:
5 內存泄露經常出現(xiàn)的例子
- 內存泄露(Memory Leak)
進程中某些對象已經沒有使用價值了,但是他們卻還可以直接或者間接地被引用到GC Root導致無法回收通殃。當內存泄露過多的時候度液,再加上應用本身占用的內存,日積月累最終就會導致內存溢出OOM.
- 內存溢出(OOM)
當應用占用的heap資源超過了Dalvik虛擬機分配的內存就會內存溢出画舌。比如:加載大圖片堕担。
-
5.1 靜態(tài)變量引起的內存泄露
當調用getInstance時,如果傳入的context是Activity的context曲聂。只要這個單
利沒有被釋放霹购,那么這個Activity也不會被釋放一直到進程退出才會釋放。public class CommUtil { private static CommUtil instance; private Context context; private CommUtil(Context context){ this.context = context; } public static CommUtil getInstance(Context mcontext){ if(instance == null){ instance = new CommUtil(mcontext); } // else{ // instance.setContext(mcontext); // } return instance; }
-
-
5.2.非靜態(tài)內部類引起內存泄露
(包括匿名內部類)錯誤的示范:public void loadData(){//隱式持有MainActivity實例朋腋。MainActivity.this.a new Thread(new Runnable() { @Override public void run() { while(true){ try { //int b=a; Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); }
解決方案
將非靜態(tài)內部類修改為靜態(tài)內部類齐疙。(靜態(tài)內部類不會隱士持有外部類)
-
當使用軟引用或者弱引用的時候,MainActivity難道很容易或者可以被GC回收嗎旭咽?GC回收的機制是什么贞奋?
當MainActivity不被任何的對象引用,才會被回收穷绵。雖然Handler里面用的是軟引用/弱引用轿塔,但是并不意味著不存在其他的對象引用該MainActivity。我連MainActivity都被回收了请垛,那他里面的Handler還玩?zhèn)€屁催训。
-
5.3.不需要用的監(jiān)聽未移除會發(fā)生內存泄露
// tv.setOnClickListener();//監(jiān)聽執(zhí)行完回收對象
//add監(jiān)聽,放到集合里面
tv.getViewTreeObserver().addOnWindowFocusChangeListener
(new ViewTreeObserver.OnWindowFocusChangeListener() {
@Override
public void onWindowFocusChanged(boolean b) {
//監(jiān)聽view的加載宗收,view加載出來的時候漫拭,計算他的寬高等。//計算完后混稽,一定要移除這個監(jiān)聽 tv.getViewTreeObserver().removeOnWindowFocusChangeListener(this); } }); 例子2: SensorManager sensorManager = getSystemService(SENSOR_SERVICE); Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL); sensorManager.registerListener(this,sensor,SensorManager.SENSOR_DELAY_FASTEST); //不需要用的時候記得移除監(jiān)聽 sensorManager.unregisterListener(listener);
-
-
- 5.4.資源未關閉引起的內存泄露情況
比如:BroadCastReceiver采驻、Cursor、Bitmap匈勋、IO流礼旅、自定義屬性attribute attr.recycle()回收。
當不需要使用的時候洽洁,要記得及時釋放資源痘系。否則就會內存泄露。 - 5.5.無限循環(huán)動畫
沒有在onDestroy中停止動畫饿自,否則Activity就會變成泄露對象汰翠。
比如:輪播圖效果龄坪。
6 常用代碼片段
6.1 Handler防止內存泄漏
如果Handler在Activity退出的時候,有可能還活著复唤,這時候就會一直持有Activity健田。
private static class MyHandler extends Handler{
// private MainActivity mainActivity;//直接持有了一個外部類的強引用,會內存泄露
//設置軟引用保存佛纫,當內存一發(fā)生GC的時候就會回收妓局。
private WeakReference<MainActivity> mainActivity;
public MyHandler(MainActivity mainActivity) {
this.mainActivity = new WeakReference<MainActivity>(mainActivity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
MainActivity main = mainActivity.get();
if(main==null||main.isFinishing()){
return;
}
switch (msg.what){
case 0:
//加載數據
//MainActivity.this.a;//有時候確實會有這樣的需求:需要引用外部類的資源。怎么辦呈宇?
int b = main.a;
break;
}
}
};
6.2 弱引用的使用
當垃圾回收時就會釋放那些沒有被引用的內存好爬。
如下面,當key值View沒有被引用時攒盈,那么map集合就會丟棄次值抵拘。
public class ListenerCollector {
/**
* WeakHashMap,此種Map的特點是型豁,當除了自身有對key的引用外僵蛛,此key沒有其他引用那么此map
* 會自動丟棄此值
* 例如:int a = 1;
* Map weakmap = new WeakHashMap();
* weakmap.put(a, "aaa");z x
* a = null
* //此時weakmap里面的a會被丟棄。
*/
static private WeakHashMap<View, MyView.MyListener> sListener = new WeakHashMap<>();
public void setsListener(View view, MyView.MyListener listener) {
sListener.put(view, listener);
}
public static void clearListeners() {
//移除所有監(jiān)聽迎变。
sListener.clear();
}
}
6.3 使匿名內部類變成靜態(tài)匿名內部類
靜態(tài)匿名內部類不屬于外部類充尉,不持有外部類的引用。下面的線程有可能在activity已經被finish了衣形,還沒有執(zhí)行結束驼侠,因此需要將其改成static。
//加上static谆吴,里面的匿名內部類就變成了靜態(tài)匿名內部類
//隱士持有MainActivity實例倒源。MainActivity.this.a
public static void loadData(){
new Thread(new Runnable() {
@Override
public void run() {
while(true){
try {
int b=a;
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}