最近工作项目中出现了内存泄露,于是找来MAT分析,学习了下。之前学习使用MAT时一直不知道怎么分析,啥时候获取hprof文件等,一直对MAT的使用感觉云里雾里,直到昨天,成功的解决了内存泄露的问题才稍稍知道了。
具体怎么分析推荐两篇文章,一篇郭大神的http://blog.csdn.net/guolin_blog/article/details/42238633,一篇夏大神的http://blog.csdn.net/xiaanming/article/details/42396507 我也是看的这两篇文章才慢慢懂了。这两篇文章里面都对MAT的分析讲的很清楚,但有个问题,两者都没有提到,就是在什么时候去dump java heap.正是这个重要的点没有讲到,我一直对MAT感觉是块鸡肋,一直不知道怎么使用。
首先要注意的是如果你要排查一个类里面的内存泄露,你首先要退出这个类的时候去dump java heap ,否则如果你在这个类里面时,取得文件去分析时仍然有小红点(小红点是啥意思,具体参考上述郭大神博客),因为这个类正在被使用,所以他肯定没释放。(之前因为这个,在没有内存泄露的时候去分析hprof文件,却发现了红点,疑惑了好久)同时去dump java heap 之前要initiate gc ,如果不的话在mat的Histogram视图里面搜怀疑没释放的类的实例个数时,可能依然有多个,但在其排除软、弱、虚引用后的Path to GC root上却没有发现有表明可能存在泄露的地方。这时就会让人很疑惑,没找到内存泄露的地方,但实例却有多个,这个怎么回事呢,到底有没有泄露呢。这里我的结论是没有泄露也可能有多个实例存在,因为系统没有立刻进行垃圾回收啊,所以你在dump java heap 之前记得点下initiate gc ,过一会然后再去获取hprof文件去分析,这个时候如果仍然存在多个实例,那就说明很可能有泄露了。
在使用handler作为内部类的时候,你会得到这样的提示
handler should be static or leaks might occur
,去测试了下
MainActivity.java
public classMainActivityextendsAppCompatActivity{
MyHandlermyHandler;
@Override
protected voidonCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Buttonbutton=(Button)findViewById(R.id.button);
myHandler=newMyHandler();
myHandler.sendEmptyMessageDelayed(0,10*1000);
button.setOnClickListener(newView.OnClickListener() {
@Override
public voidonClick(View v) {
Intentintent=newIntent(MainActivity.this,Main2Activity.class);
startActivity(intent);
MainActivity.this.finish();
}
});
}
@Override
protected voidonDestroy() {
super.onDestroy();
Log.v("PLU","-----onDestroy");
// myHandler.removeCallbacksAndMessages(null);
}
classMyHandlerextendsHandler{
//WeakReference wf;
// public MyHandler(MainActivity mainActivity){
// wf=new WeakReference(mainActivity);
// }
@Override
public voidhandleMessage(Message msg) {
super.handleMessage(msg);
// MainActivity mainActivity=wf.get();
// mainActivity.log();
log();
}
}
public voidlog(){
Log.v("PLU","I AM HANDLER ");
}
}
Main2Activity.java
public classMain2ActivityextendsAppCompatActivity{
@Override
protected voidonCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
TextViewtextView=(TextView)findViewById(R.id.tv);
textView.setOnClickListener(newView.OnClickListener() {
@Override
public voidonClick(View v) {
Intentintent=newIntent(Main2Activity.this,MainActivity.class);
startActivity(intent);
Main2Activity.this.finish();
}
});
}
}
在onDestroy时没有调handler.removeMessageAndCallback, 在两个Activity之间多次跳转后,调到Main2Activity页面,点击initiate GC后点击dump java heap ,转成标准文件后在MAT里面分析,发现MainActiivty的实例为0个,为什么我没有写成注释掉的那种WeakRefrence引用的写法也没有发生内存泄露呢,主要官方说的may occur leak ,不是说一定会,是因为执行完了就释放了,只是释放的晚一些。在调到Main2Activity页面时此时MainActivity类还没有释放,可以看到仍然有打印MainActivity里面的log,在处理完消息后MainActivity也释放了,如果MainActivity里面handler的handleMessage里面是个死循环一直在打印的话,那MainActivity就一直存在,就释放不掉。在多次切换后就会发生内存泄露,Mat分析时会发现handler持有MainActiivty,存在多个MainActivity实例。想当然的认为,如果将MainActivity的onDestroy的myHandler.removeCallbacksAndMessages(null);解开注释的话,MainActivity也不会发生内存泄露,不会有多个MainActivity实例。后来测试下发现自己错了。该Message在消息队列里面无限循环,导致点击按钮都没有反应。后来将MainActivity.java的内容改为下面这样,
public classMainActivityextendsAppCompatActivity{
MyHandlermyHandler;
HandlerThreadhandlerThread;
@Override
protected voidonCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Buttonbutton=(Button)findViewById(R.id.button);
handlerThread=newHandlerThread("plu");
handlerThread.start();
myHandler=newMyHandler(MainActivity.this,handlerThread.getLooper());
myHandler.sendEmptyMessageDelayed(0,10*1000);
button.setOnClickListener(newView.OnClickListener() {
@Override
public voidonClick(View v) {
Intentintent= newIntent(MainActivity.this,Main2Activity.class);
startActivity(intent);
MainActivity.this.finish();
}
});
}
@Override
protected voidonDestroy() {
super.onDestroy();
Log.v("PLU","-----onDestroy");
isRunning=false;
myHandler.removeCallbacksAndMessages(null);
handlerThread.quit();
}
static classMyHandlerextendsHandler{
WeakReferencewf;
publicMyHandler(MainActivity mainActivity,Looper looper){
super(looper);
wf=newWeakReference(mainActivity);
}
@Override
public voidhandleMessage(Message msg) {
super.handleMessage(msg);
MainActivitymainActivity=wf.get();
// mainActivity.log();
while(true) {
mainActivity.log();
}
}
}
public voidlog(){
Log.v("PLU","I AM HANDLER ");
}
}
再来回切换Activity,发现仍然会有内存泄露。又陷入疑惑中,难道死循环导致即使使用WeakRefrence,置为静态内部类,也无法回收外部Activity吗,当然实际开发中是不会有这样的需求的。只是为了更好地理解。
临时的结论是
1.即使MAT中同一类存在多个实例,也不一定就是发生可内存泄露,也许是你获取hprof文件之前没有调用initiate gc操作,系统刚好还没有去回收并不代表着回收不掉。
2.在执行死循环的内部类里面,即使将该内部类置为静态的,且使用若引用持有外部类引用,也会导致内存泄露。程序中一定要小心死循环。
3.在Android Studio 点击dump java heap 时,要处于离开怀疑泄露类页面,且点击initiate gc 后再去获取hprof文件。
网友评论