Android 内存泄漏实战
我们知道,一些不好的代码习惯会造成 android 内存泄漏,从而影响应用性能。然而,这是怎么发生的呢?本文不会老生常谈地告诉你应该怎么做;或是人为地制造泄漏代码,并在控制台打印出来等等。而是另辟蹊径,以一个曾经实际碰到的真实场景,从回收机制上深入图解为何会产生这种泄漏。力求让人“弄个明白”。
=== 本文还未完成,先占坑 ====
发现问题
线索1:
操作过程中,LogCat频繁输出GC日志。触发GC,推断可能内存泄漏。
线索2:
连续多次打开应用之后,界面卡顿,动画不流畅。
GC roots可达性
许多主流程序语言中(如Java、C#、Lisp),都是使用可达性分析来判定对象是否存活的。这个算法的基本思路就是通过一系列的称为GC根节点(GC Roots)的对象作为起始点,从这些节点开始进行向下搜索,搜索所走过的路径成为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连(用图论的话来说就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。如图1所示,对象object 5、object 6、object 7虽然互相有关联,它们的引用并不为0,但是它们到GC Roots是不可达的,因此它们将会被判定为是可回收的对象。
图1 可达性分析算法判定对象是否可回收
注意:OC,C++中就不是这样,而是使用引用计数来判断。
辅助验证工具:DDMS
DDMS可使用的用途方面:
- 设备的截屏
- 模拟地理位置
- 查看应用内存占用
可以看出每次打开 byte array块变大。
DDMS 可以将当前的内存 Dump成一个 hprof格式的文件,MAT 读取这个文件后会给出方便阅读的信息,配合它的查找,对比功能,就可以定位内存泄漏的原因。
分析工具:MAT
分析内存占用比较大的对象
Dominator Tree支配树
dominator: closest object on every path to node
关系图到支配树。 值得一提的是,当释放一个节点的时候,释放的可能比该对象大得多。 shallow heap:对象所占用的内存 retained heap:释放该对象,可释放的内存总和。 从这幅图来看,当我们释放了C节点,被释放的不止是C,还有D。因为A节点已经引用不到它了。 GC Roots:
1)线程栈中的对象;
2)JNI全局引用;
3)zygote栈中的对象。
等。
图连通与必经点的问题
Dominator Tree
设有向图G=(V,E),(G,r)是一个FlowGraph。
称(G,r)的子图T=(V,{(idom(x),x)},r)为(G,r)的Dominator Tree
换句话来说,就是以r为根,(idom(x),x)为边的一棵树。 性质:显而易见的,xdomy当且仅当x为y的一个祖先。
Bitmap 到GC Roots的引用路径 GC Roots ——→ Thread ——→ Activity ——→ Bitmap 当前界面已退出,但Thread 仍持有Activity的引用,导致Activity 和它引用的内存都不能被回收。
Histogram
它按类名将所有的实例对象列出来,点击表头进行排序,在表的第一行输入正则表达式 TestActivity 来匹配结果 :
探究原因
Thread是Activity的内部类,它隐式地持有着 Activity的实例。
根本原因:长生命周期对象(Thread)持有短生命周期对象(Activity)的引用,导致短生命周期对象无法释放。
这种情况还有很多,比如:
Array
大规模浮点数计算
文件存取
解决办法
onDestroy发送信号给Thread,needStop置为true。
将Thread 移到后台服务,使 Thread与Activity解除依赖。
本文系冰洁原创,遵循 署名-非商业性知识共享进行许可. 转载请在文章开头显眼处注明注明作者和出处 【冰洁】http://www.bingjie.me
人生在于体验,体验下打赏吧:)