內存泄漏分析:
往往做項目的時候情況非常復雜屁商,或者項目做得差不多了想起來要性能優(yōu)化檢查下內存泄露绢彤。
如何找到項目中存在的內存泄露的這些地方呢咸包?
1.確定是否存在內存泄露
1)Android Monitors的內存分析
最直觀的看內存增長情況,知道該動作是否發(fā)生內存泄露杖虾。
動作發(fā)生之前:GC完后內存1.4M; 動作發(fā)生之后:GC完后內存1.6M
2)使用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中的對象信息查近。
- MAT分析hprof來定位內存泄露的原因所在眉踱。(哪個對象持有了上面懷疑出來的發(fā)生泄露的對象)
1)Dump出內存泄露“當時”的內存鏡像hprof,分析懷疑泄露的類霜威;
2)把上面2得出的這些嫌疑犯一個一個排查個遍谈喳。步驟:
(1)進入Histogram,過濾出某一個嫌疑對象類
(2)然后分析持有此類對象引用的外部對象(在該類上面點擊右鍵List Objects--->with incoming references)
(3)再過濾掉一些弱引用戈泼、軟引用婿禽、虛引用,因為它們遲早可以被GC干掉不屬于內存泄露
(在類上面點擊右鍵Merge Shortest Paths to GC Roots--->exclude all phantom/weak/soft etc.references)
(4)逐個分析每個對象的GC路徑是否正常
此時就要進入代碼分析此時這個對象的引用持有是否合理矮冬,這就要考經驗和體力了谈宛!
(比如上課的例子中:旋轉屏幕后MainActivity有兩個,肯定MainActivity發(fā)生泄露了胎署,
那誰導致他泄露的呢吆录?原來是我們的CommonUtils類持有了旋轉之前的那個MainActivity他,
那是否合理琼牧?結合邏輯判斷當然不合理恢筝,由此找到內存泄露根源是CommonUtils類持有了該MainActivity實例造成的。
怎么解決巨坊?罪魁禍首找到了撬槽,怎么解決應該不難了,不同情況解決辦法不一樣趾撵,要靠你的智慧了侄柔。)
判斷一個應用里面內存泄露避免得很好共啃,怎么看?
當app退出的時候暂题,這個進程里面所有的對象應該就都被回收了移剪,尤其是很容易被泄露的(View,Activity)是否還內存當中薪者。
可以讓app退出以后纵苛,查看系統(tǒng)該進程里面的所有的View、Activity對象是否為0.
工具:使用AndroidStudio--AndroidMonitor--System Information--Memory Usage查看Objects里面的views和Activity的數量是否為0.
發(fā)現任然有兩個activity沒有關閉
==================內存泄漏經常出現的例子=====================
內存泄漏(Memory Leak):
進程中某些對象已經沒有使用價值言津,但是他們卻還可以直接或間接地被引用到GC Root攻人,導致無法回收。
當內存泄漏過多的時候悬槽,再加上應用本身占用的內存怀吻,(日積月累)最終就會導致內存溢出OOM。
現象:當我們使用微信很長時間陷谱,也出現了程序崩潰現象烙博。(這就是內存泄漏日積月累導致OOM)
內存溢出(OOM):
當應用占用的heap資源超過了Dalvik虛擬機分配的內存就會內存溢出瑟蜈。比如:加載大量圖片烟逊。
1.靜態(tài)變量引起的內存泄漏
當調用getInstance時,如果傳入的context時Activity的context铺根。只要這個單例沒有被釋放宪躯,那么Activity也不會被釋放一直到進程退出才會釋放。
public class CommonUtils {
private static CommonUtils instance;
private Context context;
private CommonUtils(Context context) {
this.context = context;
}
public static CommonUtils getInstance(Context context) {
if (instance == null) {
instance = new CommonUtils(context);
}
return instance;
}
}
2.非靜態(tài)內部類引起內存泄漏 (包括靜態(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)內部類访雪。
public static void loadData(){
new Thread(new Runnable() {
@Override
public void run() {
while (true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
在比如用弱引用解決Handler內存泄漏問題
public class MainActivity extends AppCompatActivity {
int a;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
private static class MyHandler extends Handler {
/**
* 1.直接持有一個外部類的強引用,會內存泄漏
*/
// private MainActivity mainActivity;
// public MyHandler(MainActivity mainActivity) {
// this.mainActivity = mainActivity;
// }
/**
* 2.使用弱應用
* WeakReference 是一個弱引用掂林,當所引用的對象在JVM內不再強引用時臣缀,GC后 weak reference 將會被自動回
*/
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:
//加載外部數據
int b = main.a;
break;
}
}
}
}
WeakReference --- 弱引用
WeakReference 是一個弱引用, 當所引用的對象在 JVM 內不再有強引用時, GC 后 weak reference 將會被自動回
回收時機:在垃圾回收的時候;使用:同軟引用泻帮; 生命周期:GC后終止
當時用軟應用或者弱應用的時候精置,MainActivity難道很容易或者可以被GC回收嗎?
GC回收的機制是什么锣杂?當MainActivity不被任何的對象引用脂倦。
雖然Handler里面用的是軟引用/弱引用,但是并不意味著不存在其他的對象引用該MainActivity
當MainActivity都被回收了元莫,那他里面的Handler自然沒必要在進行處理赖阻,return 即可。
Android 非靜態(tài)內部類/匿名類引起的內存泄漏
android non-static內部類導致的內存泄露
3.不需要用的監(jiān)聽未移除會發(fā)生內存泄漏
例1
final TextView tv_test = (TextView) findViewById(R.id.tv_test);
tv_test.getViewTreeObserver().addOnWindowFocusChangeListener(new ViewTreeObserver.OnWindowFocusChangeListener() {
@Override
public void onWindowFocusChanged(boolean hasFocus) {
//監(jiān)聽view的加載,view 加載出來的時候踱蠢,計算它的寬高
// 計算完后火欧,一定要移除這個監(jiān)聽
tv_test.getViewTreeObserver().removeOnWindowFocusChangeListener(this);
}
});
例2
SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL);
SensorEventListener listener = (SensorEventListener) this;
sensorManager.registerListener(listener,sensor,SensorManager.SENSOR_DELAY_FASTEST);
sensorManager.unregisterListener(listener);
4.資源未關閉引起的內存泄漏情況
對于使用了BraodcastReceiver,ContentObserver,File苇侵,Cursor离陶,Stream,Bitmap衅檀,自定義屬性attribute等資源的代碼招刨,應該在Activity銷毀時及時關閉或者注銷,否則這些資源將不會被回收哀军,造成內存泄漏沉眶。
5.無限循環(huán)動畫
沒有在onDestory中停止動畫,否則Activity就會變成泄漏對象杉适。
比如谎倔,輪播圖效果
特別感謝:
動腦學院Ricky