您的位置:首页 > 其它

内存泄露排查原因及解决方法——内存优化(五)

2015-12-23 14:21 513 查看
转载请标明出处:http://blog.csdn.net/xx326664162/article/details/50084373 文章出自:薛瑄的博客

你也可以查看我的其他同类文章,也会让你有一定的收货!

示例代码

public class NonStaticNestedClassLeakActivity extends ActionBarActivity {

TextView textView;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_non_static_nested_class_leak);
textView = (TextView)findViewById(R.id.textview);
Handler handler = new Handler();

handler.postDelayed(new Runnable() {
@Override public void
textView.setText("Done");
}//a mock for long time work
}, 800000L);

}
}


打开MAT,进行分析

非静态内部类惹的祸

MAT是对java heap中变量分析的一个工具,它可以用于分析内存泄露。

点击OQL图标

在窗口输入select * from instanceof android.app.Activity并按Ctrl + F5或者!按钮(这个操作和在这篇文章的第三步操作的结果是一样)

奇迹出现了,现在你发现泄露了许多的activity

我们来分析一下为什么GC没有回收它



图一

在OQL(Object Query Language)窗口下输入的查询命令可以获得所有在内存中的Activities

点击一个activity对象,右键选中Path to GC roots



图二

注意:这里红色方框中左下方的黄色小点,代表有GC Root



在打开的新窗口中,可以发现三个GC Root,

第一个是弱引用可以选中在左边的信息中查看

第二个GC Root 消息队列:

一旦你把Runnable或者Message发送到Handler中,它就会被放入LooperThread的消息队列,并且被保持引用,直到Message被处理。发送postDelayed这样的消息,你输入延迟多少秒,它就会泄露至少多少秒。而发送没有延迟的消息的话,当队列中的消息过多时,也会照成一个临时的泄露。

第三个GC Root 主线程:

Activity是被this$0 所引用的,它实际上是匿名类对当前类的引用。

this$0引用callback

接着callback又被Message中一串的next所引用,最后到主线程(< java local>)才结束。

任何情况下你在class中创建非静态内部类,内部类会(自动)拥有对当前类的一个强引用。

后两个GC Root都是由postDelayed()发送消息延迟造成的

解决方法:

1、尝试使用static inner class来解决

现在把Runnable变成静态的class

public class MainActivity extends AppCompatActivity {

TextView textView;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView) findViewById(R.id.textView);
Handler handler = new Handler();
handler.postDelayed(new DoRunable(textView),900000);

}

private static final class DoRunable implements Runnable {
private TextView textView;
public DoRunable(TextView textView) {
this.textView = textView;
}

@Override
public void run() {
textView.setText("Done");

}
}
}


摇一摇手机,在Android studio中触发垃圾回收,导出内存

下面三幅图中,请注意红色方框中的内容,这是在图一的查询结果上,右键选择菜单项的结果,比如:图二中的选择,跳出新的界面,将鼠标放到标题栏上,将会处在这个小提示(标题栏的完整描述)

选择Path to GC Roots ->With all references



选择Merge Shortest Paths to GC Roots. -> exclude all weak/soft references



选择Merge Shortest Paths to GC Roots. ->With all references



比较三幅图发现,结果都不一样,原因如下:

TablesAre
Path to GC Roots-with all references从GC Roots节点到该对象的引用路径,包含所有引用类型
Merge Shortest Paths to GC Roots -> exclude all weak/soft references从该对象到GC Roots节点的最短引用路径,去除所有弱引用,其他类似
Merge Shortest Paths to GC Roots-with all references从该对象到GC Roots节点最短引用路径,包含所有引用类型
图一是所有的引用

图二是从该对象到GC Roots节点的最短引用路径,但是过滤掉弱引用和软引用,因为图三所示的引用属于弱引用,所有被过滤掉

图三是从该对象到GC Roots节点的最短引用路径,不过滤任何引用,在这里显示所有引用中,路径最短的一个,所以显示图中的情形

按道理说,在导出内存前,手动触发垃圾回收,应该不会有弱引用,但不知道为何这里依旧存在,估计是要多次触发。

在图二中,发现对象被textView引用了mContext,该引用是强引用,所以不能回收。

原作者的图,是选择了Merge Shortest Paths to GC Roots ->exclude all phantim/weak/soft etc. references



2、使用弱引用 + static Runnable

现在我们把刚刚内存泄露的罪魁祸首 - TextView改成弱引用。



再次注意我们对TextView保持的是弱引用,现在让它运行,摇晃手机,不手动触发GC



再次注意我们对TextView保持的是弱引用,现在让它运行,摇晃手机,手动触发GC



使用Merge Shortest Paths to GC Roots ->exclude all phantim/weak/soft etc. references,查看这两个对象





但为什么还是有两个实例,我也不明白,求高人指教

我们应该记住:

使用静态内部类

Handler/Runnable的依赖要使用弱引用。

当把postDelayed设置为一个短时间,比如50ms的情况下,写这么多代码就有点亏了。还有一个更简单的方法,如下。

3、onDestroy中手动控制声明周期

Handler可以使用removeCallbacksAndMessages(null),它将移除这个Handler所拥有的RunnableMessage

在第二种的基础上,加入下面代码:

//Fixed by manually control lifecycle
@Override protected void onDestroy() {
super.onDestroy();
myHandler.removeCallbacksAndMessages(null);
}


横竖摇动手机,没有手动触发GC机制,依然是会存在很多实例,但是可以被GC回收,说明不是泄露

横竖摇动手机,手动触发GC机制,导出内存

这里得到的结果,依然是两个实例,原作者的结果是一个实例



使用Merge Shortest Paths to GC Roots ->exclude all phantim/weak/soft etc. references,查看这两个对象





这两个实例不是自己写的代码,所导致的泄露,所以该方法可行。

看到这两个实例,都是对系统view的引用,至于为什么会出现这样,请高人指点一下

结论

在Handler中使用postDelayed需要额外的注意,为了解决问题,我们有三种方法

使用静态内部Handler/Runnable + 弱引用

在onDestory的时候,手动清除Message

使用Badoo开发的第三方的 WeakHandler

这三种你可以任意选用,第二种看起来更加合理,但是需要额外的工作。第三种方法是我最喜欢的,当然你也要注意WeakHandler不能与外部的强引用共同使用。

转载:http://www.jianshu.com/p/c49f778e7acf
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: