1. 前言
今年,公司加大了对app性能优化的投入,把性能优化作为技术部门的KPI之一。在做需求的同时,我们也会分析并解决app中存在的内存泄漏。
2. 内存泄漏
内存泄漏主要是指程序运行过程中,动态分配内存之后,而没有及时将不再使用的内存释放,造成系统内存浪费,严重会影响程序性能,甚至会导致程序崩溃。这也是C/C++语言饱受诟病的特性之一。虽然Java语言引用了GC机制,使得内存泄漏的可能性大大降低,但是在某些情况下,还是有可能导致垃圾内存无法及时释放。下面分析几种Android开发过程中有可能导致的内存泄漏。
2.1 静态变量引用
1 | public class MainUtils { |
在横竖屏切换的时候,会先导致MainActivity销毁重建,然而MainUtils有一个静态变量引用了MainActivity,因此会导致内存泄漏,具体原因后面分析。
2.2 多线程
多线程也是Android中导致内存泄漏的原因之一。常见的因为多线程导致内存泄漏有如下几种
2.2.1 Handler
在Activity、Fragment或者View用Handler.PostDelayed(Runnable, 2000)执行定时操作(如每隔一分钟刷新二维码),在Activity、Fragment或者View销毁时,没有把Handler中的Message和Callback销毁导致内存泄漏。
2.2.2 AsyncTask
在Activity或者Fragment甚至是View、Adapter(正常来说View和Adapter不应该有AsyncTask)中使用了AsyncTask,在类被销毁时没有及时把AsyncTask销毁掉,导致内存泄漏。
2.2.3 TimerTask
使用了TimerTask作为定时任务,如定期查看某个文件夹或文件是否存在,但是传入了MainActivity这个实例(如2.1)导致泄漏。
2.2.4 网络操作
1 | public class MainActivity extends AppCompatActivity { |
上述例子中,执行网络操作过程中,如果用户退出Acitvity,便会导致内存泄漏。
2.3 动画
最常见的场景Activity、Fragment持有了View,在View动画还没有执行完之前退出了Fragment或者Activity,此时便会产生内存泄漏(如轮播图)。
3. 分析工具
3.1 Android Monitors
使用Android Monitors来查看内存抖动,并结合代码分析来解决内存泄漏问题是一个比较好的方案。关于hprof文件中的信息是从哪里的(JVMTI)和hprof文件的格式可以参考以下两篇文章
- HPROF: A Heap/CPU Profiling Tool
- HPROF 输出文件的说明
Android Studio中hprof文件各个字段的含义,可以参考这里:HPROF Viewer and Analyzer。
需要注意的是Android Mionitor导出的hprof文件不是标准的hprof文件,需要切换到captures,然后右键—>Export to standard hprof才能导出标准hptof文件,才能被MAT工具识别。如下图
下面我们结合Android Monitors分析例子2.1中导致的内存泄漏。假设我们怀疑某个操作会导致内存泄漏(拿旋转屏幕作为例子)
刚开始运行时,内存图如下,占用内存为2.49M:
旋转2次之后,内存发现内存发生了微小抖动,内存图如下:
之后再点几次GC
我们发现使用内存为2.55M,比操作前(2.49M)增加了0.06M,结合hprof文件就可以知道MainActivity在内存中有两个实例,但是按照正常逻辑,应该只有一个MainActivity实例,者说明发生了内存泄漏。再继续查看两个MainActivity的引领链,发现0号MainActivity实例被MainUtils一个静态变量实例引用了,这就找到了问题的根源。
3.2 LeakCanary
LeakCanary是square公司推出的一个Android和Java内存泄漏检测工具。其官方介绍为**A memory leak detection library for Android and Java.**。使用非常简单
- 在build.gradle文件中添加
1
2debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.1'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1' - 在Application中添加重新运行2.1中代码,旋转几次屏幕后,leakCanary便能检测出MainActivity发生了泄漏,如下图
1
2
3
4
5
6
7
8
9
10public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
LeakCanary.install(this);
// Normal app init code...
}
并且把泄漏类及路径都标记出来了!其内部主要是用类似于MAT的内存检测工具haha,其主要原理如下:
- RefWatcher.watch() creates a KeyedWeakReference to the watched object.
- Later, in a background thread, it checks if the reference has been cleared and if not it triggers a GC.
- If the reference is still not cleared, it then dumps the heap into a .hprof file stored on the file system.
- HeapAnalyzerService is started in a separate process and HeapAnalyzer parses the heap dump using > HAHA.
- eapAnalyzer finds the KeyedWeakReference in the heap dump thanks to a unique reference key and > locates the leaking reference.
- HeapAnalyzer computes the shortest strong reference path to the GC Roots to determine if there is a leak, and then builds the chain of references causing the leak.
- The result is passed back to DisplayLeakService in the app process, and the leak notification is shown.
看了下源码,核心原理是在每个Activity.onDestroy的时候封装成带key弱引用KeyedWeakReference,然后如下处理:
- key对应弱引用是否存在,若是,转2。若否,不存在内存泄漏
- 手动触发Gc,再次判断key对应弱引用是否存在,若是,转3。若否,不存在内存泄漏
- 分析hprof文件,再次判断key对应弱引用是否存在,若是,转4。若否,不存在内存泄漏
- 生成分析报告
就不上代码分析了,想了解更多请参考:
- https://github.com/square/leakcanary/wiki/FAQ#how-does-it-work
- LeakCanary 内存泄露监测原理研究
- LeakCanary核心原理源码浅析
3.3 MAT
MAT工具使用相对简单,具体用法可以参考文章:使用MAT分析应用的内存信息
4. 几点建议
- 对于使用Handler的类,在销毁前中调用Handler.removeCallbacksAndMessages(null);
- 对于使用动画的View,在销毁前调用View..clearAnimation();
- 使用多线程的类,销毁时需要把线程给stop.
- 对于Cursor、Bitmap、Broadcast、系统监听(如传感器监听、网络状态监听等)IO流等资源,使用完后关闭或反注册。
5. 参考
Android Studio - HPROF文件查看和分析工具
Android 内存使用分析和程序性能分析
HPROF 输出文件的说明
JVMTI 和 Agent 实现
基于 JVMTI 实现 Java 线程的监控