我们都知道虽然Java有垃圾回收机制,但是也不能完全杜绝内存泄漏这些问题,从而会导致OOM。下面就会用到DDMS和MAT工具来进行内存分析,MAT的全称是Eclipse Memory Analyzer,是一款强大的内存泄漏分析工具,不需要安装解压之后直接使用就可以了,只需要把hprof文件导入进去就可以使用。下面先看一些名词解释
Heap Size:手机给每一个程序分配的可使用内存大小,如果超过这个大小就会OOM,当然不同的手机这个值会不同。
GC:Garbage Collection,也就是垃圾回收,Android中的垃圾回收机制并不能防止内存泄漏的出现,导致内存泄漏最主要的原因就是某些长存对象持有了一些其它应该被回收的对象的引用,导致垃圾回收器无法去回收掉这些对象,那也就出现内存泄漏了。
Shallow Size
对象自身占用的内存大小,不包括它引用的对象。
针对非数组类型的对象,它的大小就是对象与它所有的成员变量大小的总和。当然这里面还会包括一些java语言特性的数据存储单元。
针对数组类型的对象,它的大小是数组元素对象的大小总和。
Retained Size
Retained Size=当前对象大小+当前对象可直接或间接引用到的对象的大小总和。(间接引用的含义:A->B->C, C就是间接引用)
换句话说,Retained Size就是当前对象被GC后,从Heap上总共能释放掉的内存。
不过,释放的时候还要排除被GC Roots直接或间接引用的对象。他们暂时不会被被当做Garbage。
下面模拟一个会产生内存泄漏的场景来具体看看怎么使用
public class MainActivity extends AppCompatActivity {
private static Context context;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
context = this;
//开启线程
new MyThread().start();
}
public class MyThread extends Thread {
@Override
public void run() {
super.run();
//模拟耗时操作
try {
Thread.sleep(10 * 60 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
这里context是用static修饰的,在onCreate()里面开启了一个线程,这个线程里面有个耗时的任务,显然如果这个Activity销毁了context和这个线程都不会销毁,所以这个Activity也不会被释放。现在反复的旋转几次手机,然后查看DDMS,如下所示:
然后点击上面箭头所示的按钮Dump Java Heap,等一段时间就会生成一个hprof文件,再点击左边的Captures,右键生成的hprof文件,然后选择导出,如下图所示:
好了,这里导出的文件就可以用MAT来打开,打开之后如下所示:
这里面的功能确实比较多,我们只说说这里比较常用的两个就好了
Histogram可以列出内存中每个对象的名字、数量以及大小。
Dominator Tree会将所有内存中的对象按大小进行排序,并且我们可以分析对象之间的引用结构。
现在点击Histogram
这里是把当前应用程序中所有的对象的名字、数量和大小全部都列出来了,需要注意的是,这里的对象都是只有Shallow Heap而没有Retained Heap的,那么Shallow Heap又是什么意思呢?就是当前对象自己所占内存的大小,不包含引用关系的,比如说上图当中,byte[]对象的Shallow Heap最高,说明我们应用程序中用了很多byte[]类型的数据,比如说图片。可以通过右键 -> List objects -> with incoming references来查看具体是谁在使用这些byte[]。
现在在第一行的正则表达式输入框输入MainActivity回车
接下来对着MainActivity右键 -> List objects -> with incoming references查看具体MainActivity实例,如下图所示:
如果想要查看内存泄漏的具体原因,可以对着任意一个MainActivity的实例右键 -> Path to GC Roots -> exclude weak references,结果如下图所示:
这里看到有些左下角有小黄点,带有这个小黄点的就表示可以被GC ROOT访问到,可以被GC Root访问到的对象是不可以被回收的,当然不代表带有小黄点的就是内存泄漏的,也有些是一直被系统所使用的。
所以从这里看到就找到了内存泄漏的原因。
下面看看Histogram
下面的hprof文件是我写的一个项目里面的
可以看到占用内存最多的是第一行的BitmapState,并且它的左下角没有小黄点,之前说过没有代表着可以被回收,那么是否代表一定不会有内存泄漏呢,当然不是的,所以这里要继续进行下一步操作:右键这一行-->Path to GC Roots -->exclude weak/soft references
这时就可以明显看到内存泄漏的原因了,是在一个叫MapRoundActivity的界面里面,前三个都有小黄点不能被回收,这里的思路就是这样的,但是这里其实也不算内存泄漏因为在这三个后面都是显示的Systen Class,代表着它被系统所占用,这里可能没有找到合适的例子,所以。。。