内存泄露排查原因及解决方法——内存优化(五)
2015-12-23 14:21
513 查看
转载请标明出处:http://blog.csdn.net/xx326664162/article/details/50084373 文章出自:薛瑄的博客
你也可以查看我的其他同类文章,也会让你有一定的收货!
示例代码
点击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()发送消息延迟造成的
摇一摇手机,在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
比较三幅图发现,结果都不一样,原因如下:
图一是所有的引用
图二是从该对象到GC Roots节点的最短引用路径,但是过滤掉弱引用和软引用,因为图三所示的引用属于弱引用,所有被过滤掉
图三是从该对象到GC Roots节点的最短引用路径,不过滤任何引用,在这里显示所有引用中,路径最短的一个,所以显示图中的情形
按道理说,在导出内存前,手动触发垃圾回收,应该不会有弱引用,但不知道为何这里依旧存在,估计是要多次触发。
在图二中,发现对象被textView引用了mContext,该引用是强引用,所以不能回收。
原作者的图,是选择了Merge Shortest Paths to GC Roots ->exclude all phantim/weak/soft etc. references
再次注意我们对TextView保持的是弱引用,现在让它运行,摇晃手机,不手动触发GC
再次注意我们对TextView保持的是弱引用,现在让它运行,摇晃手机,手动触发GC
使用Merge Shortest Paths to GC Roots ->exclude all phantim/weak/soft etc. references,查看这两个对象
但为什么还是有两个实例,我也不明白,求高人指教
我们应该记住:
使用静态内部类
Handler/Runnable的依赖要使用弱引用。
当把postDelayed设置为一个短时间,比如50ms的情况下,写这么多代码就有点亏了。还有一个更简单的方法,如下。
在第二种的基础上,加入下面代码:
横竖摇动手机,没有手动触发GC机制,依然是会存在很多实例,但是可以被GC回收,说明不是泄露
横竖摇动手机,手动触发GC机制,导出内存
这里得到的结果,依然是两个实例,原作者的结果是一个实例
使用Merge Shortest Paths to GC Roots ->exclude all phantim/weak/soft etc. references,查看这两个对象
这两个实例不是自己写的代码,所导致的泄露,所以该方法可行。
看到这两个实例,都是对系统view的引用,至于为什么会出现这样,请高人指点一下
使用静态内部Handler/Runnable + 弱引用
在onDestory的时候,手动清除Message
使用Badoo开发的第三方的 WeakHandler
这三种你可以任意选用,第二种看起来更加合理,但是需要额外的工作。第三种方法是我最喜欢的,当然你也要注意WeakHandler不能与外部的强引用共同使用。
转载:http://www.jianshu.com/p/c49f778e7acf
你也可以查看我的其他同类文章,也会让你有一定的收货!
示例代码
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变成静态的classpublic 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
比较三幅图发现,结果都不一样,原因如下:
Tables | Are |
---|---|
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所拥有的Runnable与Message。在第二种的基础上,加入下面代码:
//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
相关文章推荐
- MODBUS ASCII和RTU
- Java 接口和抽象类---不可不知的异同点
- 解析assets里的xml文件
- co 函数库的含义和用法
- IOS 颜色创建
- [leetcode] 213. House Robber II 解题报告
- oracle分区表【转】
- 《Java启程,想一探编程世界》
- Thunk 函数的含义和用法
- 希尔排序 快速排序 堆排序的实现
- 如何在xcode中手动添加.pch文件
- HttpClient&HttpURLConnection
- oracle12c安装详细图解
- ubuntu安装jdk
- 三目运算与ifelse
- Android一些总结
- Generator 函数的含义与用法
- 例题8-5 UVA 11054 Gergovia的酒交易
- iOS 图片简单处理方法
- js获取项目根路径