前言

我们知道Android系统会分配内存给我的app,但是不能全部内存都分配给你的会有一个内存上限,也就是俗称的堆大小(Heap Size)。虽然说现在随着手机硬件设备的不断提高,手机内存得Heap Size也有所提升,可是内存泄漏的问题还是不能忽视。开发中时常会遇到内存泄漏的问题,一般往往在自己开发中不知情的情况下,因为编码问题造成,所以学习性能优化后,我们在编码阶段就应该减少这一方面的错误,从而减少后期检测的时间。

什么是内存泄漏?

当一个对象已经不需要使用了,本该被回收时,而有另外一个正在使用的对象持有它的引用,从而导致了对象不能被GC回收。这种导致了本该被回收的对象不能被回收而停留在堆内存中,就产生了内存泄漏。



内存溢出

当应用程序内存泄漏GCRoot没办法回收,再加上应用程序本身所见的内存超过了Dalvik虚拟机分配的内存就会内存溢出(OutofMemory)。

内存泄漏带来的影响

1.应用卡顿
泄漏的内存影响了GC的内存分配,过多的内存泄漏会影响应用的执行效率自带的Monitors
2.应用异常(OOM)
过多的内存泄漏,最终会导致 Dalvik分配的内存,出现OOM

检查工具

1.Android Studio自带的Monitors

2.Eclipse的MAT(MemoryAnalyzer)工具,不一定要在Eclipse中使用,可以在单独的下载使用

  MAT工具的下载地址为:http://www.eclipse.org/mat/downloads.php

3.Android端的Leakcanary,需要在项目中导入框架,然后随着app安装在桌面生成一个对应的内存泄漏检测app

内存泄漏分析

使用Android Studio Monitors

Android开发dialog设置最大高度 android heapsize_内存泄漏

运行程序后,打开右下角的Android Monitor 一般看到的logcat能看到log信息,然后点击Monitors,我们看到的是关于Memory,CPU,Network,GPU的监控,展开Memory界面能看到应用程序在手机上使用内存的情况,那么怎么检测我们的应用程序的内存泄漏呢?一般来说当退出一个页面的时候,如果内存没有降下来的时候,可能就发生内存泄漏,我们可以尝试反复的进入退出这个页面,你会发现应用程序慢慢占用的手机内存会越来越多,这时候我们手动的发起一次GC操作,然后dump java heap(上图的第3和第4步),当程序dump成功后会自动打开一个horof文件,如下图


Android开发dialog设置最大高度 android heapsize_应用程序_02

然后点击右上角的按钮进行分析,从分析的结果那里会告诉你哪个Activity出现了内存泄漏,一步一步的点击后可以在Reference Tree中找到内存泄漏的地方,从上图来看是SharedConfig类一直引用了SplashActivity的实例,导致SplashActivity无法被回收。

不过Android Studio的这个内存分析还不是很完善,有些东西还是分析不出来的,这时候我们使用一下Eclipse的插件第三方工具MemoryAnalyzer。

Android开发dialog设置最大高度 android heapsize_Android_03

这时候有个问题那就是Android Studio 生成的.hprof文件是MemoryAnalyzer是无法识别的,需要我们在Captures目录里面右击Export to standard .hprof导出一个MemoryAnalyzer能够识别的.hprof文件。


使用MemoryAnalyzer工具(File-->Open Heap Dump)打开刚才导出的.hprof文件,如下图所示

Android开发dialog设置最大高度 android heapsize_Android_04

刚接触的时候可能有点摸不着头脑,而且MAT并不会准确地告诉我们那里出现内存泄漏,它会提供一些怀疑的对象和线索,需要我们自己去分析这些信息来确定。

通常我们会用到比较多的功能是Histogram和Dominator Tree

Histogram  可以列出内存中每个对象的名字、数量、对象释放的内存、对象所占用的内存

Dominator Tree  可以分析对象之间的引用关系,如果文件图标左下角个红点说明这里不能被回收

Top consumers 通过图形列出最大的object

Leak Suspects  通过MAT自动分析泄漏的原因

如何分析

1.Histogram

点击Action下的Histogram,生成下图

Android开发dialog设置最大高度 android heapsize_Android_05

我们可以直接在ClassName那里输入怀疑的对象,比如SplashActivity,如下图所示

Android开发dialog设置最大高度 android heapsize_Android_06

值得注意的是它这个是大小写区分的,而$2指的是匿名内部类,接下来对着SplashActivity右键 -> List objects -> with incoming references查看具体SplashActivity实例,如下图所示:

Android开发dialog设置最大高度 android heapsize_应用程序_07

我们可以看到许多类都有SplashActivity的引用,我们知道除了强引用之外,其他的都是可已被GC回收掉的,那么如果我们想要进一步的查看SplashActivity泄漏的原因,可以对着SplashActivity的实例右键 -> Path to GC Roots -> exclude all phantom/weak/soft ect references(排除掉所有虚引用、弱引用和软引用),因为强引用才会导致对象无法释放, 结果如下图所示:

Android开发dialog设置最大高度 android heapsize_Android_08

可以看到类ShareConfig持有了SplashActivity的示例导致其无法被回收造成内存泄漏。

MAT 还可以对比两个Histogram,对了前提是两个.hprof文件,将两个文件都添加到 Compare Basket 里面: 

Android开发dialog设置最大高度 android heapsize_Android_09

点击右上角的“!”执行对比:

Android开发dialog设置最大高度 android heapsize_应用程序_10

之后会生成对比文件:

Android开发dialog设置最大高度 android heapsize_应用程序_11

然后直接输入包名“com.example”进行筛选:

Android开发dialog设置最大高度 android heapsize_Android_12

这里说明一下情况,第一个.hprof文件是直接MainActivity没有进入SecondActivity,而第二个是反复进入退出SecondActivity,很明显退出SecondActivity对象并没有被回收,这里就值得怀疑了,右键List objects-->with incoming references -> Path to GC Roots -> exclude all phantom/weak/soft ect. references:

Android开发dialog设置最大高度 android heapsize_Android_13

可以看到是SecondActivity退出的时候某个线程还在运行(线程是内部类会隐式持有外部类Activity的对象),导致Activity对象无法被回收。

2.Dominator Tree 

同样的点击Action下的Dominator Tree,生成下图

Android开发dialog设置最大高度 android heapsize_内存泄漏_14

他会根据Retained Heap从大到小进行排列,Shallow heap指的是这个对象自身占用的内存大小,而Retained Heap指的是这个对象以及它所持有的其它引用(包括直接和间接)所占的总内存。更详细的讲解可以查看点击打开链接

可是从这是无法直接找到内存泄漏的地方的,然而占用内存最多的是不是就最值得怀疑?那么我们右击击第一个Bitmap对象-> Path to GC Roots -> exclude all phantom/weak/soft ect. references(排除掉所有虚引用、弱引用和软引用),因为强引用才会导致对象无法释放,结果如下图所示:

Android开发dialog设置最大高度 android heapsize_内存泄漏_15

可以看到,Bitmap对象经过层层引用之后,到了ShareConfig的mInstance这里,然后在图标的左下角有个红色的图标,就说明在这里可以被GC Roots访问到了,并且这是由我们自己创建的Thread,并不是System Class了,那么由于ShareConfig的mInstance能被GC Roots访问到导致不能被回收,导致它所持有的其它引用也无法被回收了,包括SplashActivity,也包括SplashActivity中所包含的图片。

PS:不是所有在文件图标左下角有个红色小点的对象都存在内存泄漏,有一些对象是系统生成的无法回收的对象(通常后面会带有System Class)。Dominator Tree 就是通过这种大对象查看他是否不能被GC回收的方式来把内存泄漏的原因找出来,毕竟占用的内存越大越值得怀疑。

使用 leakcanary

Leakcanary是手机移动端的一个内存泄漏检测库,在应用程序编译运行的时候,我们和平时一样随便点击进入退出页面操作,当发现内存泄漏的时候,它便会像推送一样在手机的通知栏上提醒我们存在内存泄漏。项目地址:https://github.com/square/leakcanary

通过官方的介绍我们可以一步一步的对这个库对我们的项目进行接入:

1.在我们项目的build.gralde文件中的dependencies添加依赖:


dependencies {
   debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5'
   releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
   testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
 }

如果你只是想在Debug模式的时候编译运行Leakcanary,那么只需添加debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5'

2.在Application中配置:

public class MyApplication extends Application {

public static RefWatcher getRefWatcher(Context context) {
MyApplication application = (MyApplication) context
            .getApplicationContext();
    return application.refWatcher;
}

private RefWatcher refWatcher;

@Override
public void onCreate() {
    super.onCreate();
    refWatcher = LeakCanary.install(this);
  
}


}

3.对你需要捕抓的对象进行监控


public class BaseActivity extends Activity {
@Override
	protected void onDestroy() {
		super.onDestroy();
		RefWatcher refWatcher = DemoApplication.getRefWatcher(this);
		refWatcher.watch(this);
		
	}
}



当编译运行了以后手机桌面上会生成对应的图标:

Android开发dialog设置最大高度 android heapsize_应用程序_16

,最多记录7个记录,当存在内存泄漏的时候通知栏会有类似推送的消息

Android开发dialog设置最大高度 android heapsize_内存泄漏_17

,点击效果如下:

Android开发dialog设置最大高度 android heapsize_Android_18

Memory Usage 

通过 Android studio 的 Memory Usage 功能进行查看:

Android开发dialog设置最大高度 android heapsize_应用程序_19

Android开发dialog设置最大高度 android heapsize_内存泄漏_20

反复的进入退出SecondActivity3次,整个过程就前后2个Activity,现在却有4个Activity实例,这里就需要值得怀疑的地方,接下来就需要MAT或者Mioniter进行内存分析了