Android 内存泄露简介、典型情景及检测解决
来源:互联网 发布:btob 知乎 编辑:程序博客网 时间:2024/06/11 01:54
什么是内存泄露?
Android虚拟机的垃圾回收采用的是根搜索算法。GC会从根节点(GC Roots)开始对heap进行遍历。到最后,部分没有直接或者间接引用到GC Roots的就是需要回收的垃圾,会被GC回收掉。内存泄漏指的是进程中某些对象(垃圾对象)已经没有使用价值了,但是它们却可以直接或间接地引用到gc roots导致无法被GC回收。无用的对象占据着内存空间,导致不能及时回收这个对象所占用的内存。内存泄露积累超过Dalvik堆大小,就会发生OOM(OutOfMemory)。
内存泄露的经典场景
非静态内部类的静态实例
由于内部类默认持有外部类的引用,而静态实例属于类。所以,当外部类被销毁时,内部类仍然持有外部类的引用,致使外部类无法被GC回收。因此造成内存泄露。
举个栗子
private static Leak mLeak; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_second); mLeak = new Leak(); } class Leak { }
错误栗子说明:static
关键字修饰mLeak
属性,将mLeak
存在静态区中,而Leak
为内部类,默认持有外部类的引用。当Activity
销毁时,mLeak
紧紧抱住Activity
的大腿深情告白:“MLGB!劳资就是不放你走!”。斗不过mLeak
属性的GC,自然不敢回收二手娘们Activity
。因此造成内存泄露。
不正确的Handler
错误代码示例:
private MyHandler mMyHandler; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_second); mMyHandler = new MyHandler(); mMyHandler.sendMessageDelayed(new Message(), 10 * 1000); } class MyHandler extends Handler { @Override public void handleMessage(Message msg) { super.handleMessage(msg); } }
正确写法如下:
private MyHandler mMyHandler; static class MyHandler extends Handler { WeakReference<Activity> mActivityWeak; MyHandler(Activity act) { mActivityWeak = new WeakReference<Activity>(act); } @Override public void handleMessage(Message msg) { super.handleMessage(msg); if (mActivityWeak.get() != null) { // doSomething } } }
我们知道在handler.sendMessage(msg)
时,msg.target
会指向handler
,msg
会插入MessageQueue
。此为下面讲解的基础,对这部分不太熟悉的同学可以参考这篇博客。
错误之处
MyHandler为内部类,默认持有外部类的引用。当Activity
销毁时,如果MessageQueue
中仍有未处理的消息,那么mMyHandler
示例将继续存在。而mMyHandler
持有Activity
的引用。故Activity
无法被GC回收。
正确解析
static
关键字修饰MyHandler
类,使MyHandler不持有外部类的引用。使用WeakReference<activity>
保证当activity
销毁后,不耽误gc回收activity
占用的内存空间,同时在没被销毁前,可以引用activity
。
管它正确错误都让它正确
通过上面的分析,可以得出结论:Handler造成内存泄露时,是因为MessageQueue
中还有待处理的Message
,那我们在Activity#onDestroy()
中移除所有的消息不完事了嘛。反正Activity
都销毁了,MessageQueue
中的msg
也就什么存在的意义了,可以移除。代码如下:
@Override protected void onDestroy() { super.onDestroy(); // 移除所有的callback和msg mMyHandler.removeCallbacksAndMessages(null); }
静态变量引起内存泄露
这里以单例模式引起Context泄露为例
public class Singleton { private static Singleton instance; private Singleton(Context context){ } public static Singleton getInstance(Context context){ if (instance == null){ synchronized (Singleton.class){ if (instance == null){ instance = new Singleton(context); } } } return instance; }}
错误之处
在调用Singleton#getInstance()
方法如果传入了Activity
。如果instance
没有释放,那么这个Activity
将一直存在。因此造成内存泄露。
修正版
将new Singleton(context)
改为new Singleton(context.getApplicationContext())
即可,这样便和传入的Activity
没撒关系了。该释放释放、该回家回家。
碎碎念
- 当使用
Cursor
、File
、Socket
等资源时往往都使用了缓冲。在不需要的时候应该及时关闭它们,收回所占的内存空间。 Bitmap
不用就recycle
掉。注意调用recycle
后并不意味着立马recycle
,只是告诉虚拟机:小子,该干活咯!ListView
一定要使用ConvertView
和ViewHolder
BraodcastReceiver
注册完事,不用时也要反注册内存泄露的检测
Heap工具
- 打开DDMS视图
- 选中Devices下某个具体的应用程序
- 选中Devices下第二个小绿点Update Heap
- 不断运行程序并点击Cause GC
- 关注data Object行、Toal Size列
- 耍你的APP去吧,如果发现Toal Size越来越大,很可能有内存泄露的发生
MAT(Memory Analyzer Tool)工具
导出.hprof文件
- 打开DDMS视图
- 选中Devices下某个具体的应用程序
- 选中Devices下第二个小绿点Update Heap
- 点击Cause GC
- 点击Dump HPROF file
- 切换到MAT页卡,默认如下图所示
最显眼的就是饼图了,里面列出了每种类型的数据所占大小。和红色箭头所指的Dominator有的一拼,然而这并没有什么卵用。我们的重点在Histogram。没撒说的,点击它。默认图如下
默认是按Class排序,第一行支持正则表达式。为了查看方便,下面我们会以Group by package的形式分组。正确的打开方式应该是这个样子的。
内存泄露Demo
这里以非静态内部类的静态实例为例,Demo只有两个Activity,MainActivity
中只有一个按钮,点击跳转到SecondActivity
。
public class SecondActivity extends Activity { private static Leak mLeak; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_second); mLeak = new Leak(); } class Leak { }}
查找内存泄露
启动APP,点击进入SecondActivity
,然后按back键返回到MainActivity
。打开.hprof
文件。查找我们的包名com.dyk.memoryleak
。
可以看到,虽然我们结束了SecondActivity
,但是SecondActivity
仍然存在,内存泄露无疑。
1.右键SecondActivity
,选择List Objects
—→with incoming references
结果如下图:
2.右键com.dyk.memoryleak.SecondActivity
,选择Path to GC
—→with all references
结果如下图:
可以看到是因为mLeak属性的引用导致SecondActivity
无法回收。既然找到了内存泄露的原因,通过上文的介绍,相信改起来难度应该不是很大的。
3.再次进入SecondActivity
。由于上次创建的SecondActivity
还没有被回收,可以预期到此时应该存在两个SecondActivity
实例。
关于内存泄露的内容暂时到此为止了。MAT更多的功能,请自行查找学习。感谢耐心阅读到最后~
原文:http://blog.csdn.net/qq_17250009/article/details/51261616
- Android 内存泄露简介、典型情景及检测解决
- Android 内存泄露简介、典型情景及检测解决
- Android 内存泄露简介、典型情景及检测解决
- 内存泄露及检测
- 内存泄露及检测
- 内存泄露及检测
- 内存泄露及检测
- 内存泄露及检测
- android检测内存泄露
- Android 内存泄露检测
- Android 内存泄露检测
- 内存管理及泄露检测
- 内存泄露及检测方法
- Android中内存泄露代码优化及检测
- Android开发过程中内存泄露检测及工具
- 解决android内存泄露
- Android内存泄露检测(LeakCanary)
- LeakCanary-Android内存泄露检测
- 环信Android客户端集成文档
- leetcode 66. Plus One
- 使用Mule Studio开发ESB应用 - Hello World
- 框架整合
- 代理实现机制,Java中动态代理和cglib动态代理的实现机制
- Android 内存泄露简介、典型情景及检测解决
- JavaScript入门学习二
- Oracle HS (Heterogeneous Services)深入解析 及协同Gateway工作流程
- oracle调优笔记——归档
- HTML+CSS基础(七):CSS格式化排版
- Middle-题目11:137. Single Number II
- [leetcode] 208. Implement Trie (Prefix Tree)
- ant蚂蚁配置Java项目
- C#学习笔记