我们知道,一些不好的代码习惯会造成 android 内存泄漏,从而影响应用性能。然而,这是怎么发生的呢?本文不会老生常谈地告诉你应该怎么做;或是人为地制造泄漏代码,并在控制台打印出来等等。而是另辟蹊径,以一个曾经实际碰到的真实场景,从回收机制上深入图解为何会产生这种泄漏。力求让人“弄个明白”。

=== 本文还未完成,先占坑 ====

发现问题

线索1:
image

操作过程中,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是不可达的,因此它们将会被判定为是可回收的对象。

image

图1 可达性分析算法判定对象是否可回收

注意:OC,C++中就不是这样,而是使用引用计数来判断。


辅助验证工具:DDMS

DDMS可使用的用途方面:

  1. 设备的截屏
  2. 模拟地理位置
  3. 查看应用内存占用

image

可以看出每次打开 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栈中的对象。
等。

image

图连通与必经点的问题

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 和它引用的内存都不能被回收。

image

Histogram

它按类名将所有的实例对象列出来,点击表头进行排序,在表的第一行输入正则表达式 TestActivity 来匹配结果 :

image


探究原因

class TestActivity {
	protected void onCreate(Bundle savedInstanceState) {
		LoadPicThread loadPicThread = new LoadPicThread();
		loadPicThread.start();
	}
	private class LoadPicThread extends Thread {
		@Override
		public void run() {
			super.run();
			while(!needStop) {
				...load data...
				try {
					Thread.sleep(1000 * 60 * 5);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}
}

Thread是Activity的内部类,它隐式地持有着 Activity的实例。

根本原因:长生命周期对象(Thread)持有短生命周期对象(Activity)的引用,导致短生命周期对象无法释放。

这种情况还有很多,比如:

Array

大规模浮点数计算

文件存取

解决办法

onDestroy发送信号给Thread,needStop置为true。

将Thread 移到后台服务,使 Thread与Activity解除依赖。