常见内存问题
通常来说,有3类内存问题
- 内存抖动 :内存波形图呈 锯齿状、GC导致卡顿。这个问题在dalvik虚拟机上更加明显,ART上由于做了大量的优化,内存分配和GC效率提升了5~10倍,出现内存抖动的概率小很多。
- 内存溢出 (OOM,out of memory) :是指程序在申请内存时,没有足够的内存空间供其使用,就会出现 out of memory问题。
- 内存泄露 (memory leak) :是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。memory leak 会最终会导致 out of memory!
内存分析工具
命令行
adb shell dumpsys meminfo 进程名 -d 查看当前栈中所有activity实例,如果大于理论上应该存在的Activity实例数,说明存在内存泄漏
Android profiler
3.0以前是Monitors,可以直接定位到源码
3.0之后只会生成dump文件(horof),需要自己在IDE手动按报名去分析内存泄漏。
LeakCanary原理
- 通过传入的Context,获取applicationContext,通过registActivityCallBacks,监听所有Activity的生命周期,
- 监听onDestory,将当前引用存到一个弱引用中,
- 执行一个ensureGone的方法,移除所有弱引用,调用GC,如果弱引用还存在,生成当前的dump文件
- 开启前台服务,分析dump文件,生成相应的内存泄漏信息
优点:使用简单。缺点:自身有一些bug,导致信息不准确,或者无法分析。
LeakCanary自定义用法
除了基本使用外,还可以自定义处理结果。就能实现将丰富的内存泄漏信息保存本地,或者上传到服务器进行分析。
实现步骤为
- 定义一个类继承自DisplayLeakServcie,重写afterDefaultHandling方法
1
2
3
4
5
6
7
8
9public class LeakCnaryService extends DisplayLeakServcie {
private final String TAG = “LeakCanaryService”;
protected void afterDefaultHandling(HeapDump heapDump, AnalysisResult result, String leakInfo) {
...
}
}
三个参数的定义如下:
heapDump:堆内存文件,可以拿到完成的hprof文件,以使用MAT分析。
result:监控到的内存状态,如是否泄漏等。
leakInfo:leak trace详细信息,除了内存泄漏对象,还有设备信息。
- LeakCnaryService需要在AndroidManifest清单文件中注册
- install时,使用代码
1
2
3
4
5
6
7
8
9
10
11public class BaseApplication extends Application {
public void onCreate() {
super.onCreate();
mRefWatcher = LeakCanary.install(this, LeakCanaryService.calss, AndroidExcludedRefs.createAppDefaults().build());
}
...
}
MAT
自己下载,早期是Eclipse的插件工具,优点:内容泄漏信息非常详细,可以分析OOM,缺点:步骤繁琐
- 生成2个horof文件(页面进入前和后各一个,最好开关5次前后取,可以用Android profiler生成)
- 将文件导入到MAT中,先选中Overview选项卡,再点击Histogram。再点击Histogram选项卡,右键点击Histogram按钮,把2个文件加入对比。
- 产生一个compare tables选项卡,通常分析的是byte[], 选中bytes[]-》右键-》table2-》show objects by class
- 生成一个新的class references选项卡,通常分析BitMap
- 选中-》右键Merge。。。Gc Root,倒数第2个,生成非常详细的内存泄漏点
还有一个常用选项卡dominator_tree,待研究
内存溢出
OOM的场景有:
- 数据量大的数组
- 读取大文件,比如大图片。
内存泄漏的常见原因
导致Android内存泄漏的原因主要有如下几类
资源类对象不使用时忘记调用API释放
- Bitmap忘记调用recycle()
- Closable子类忘记调用Close()方法
- Timer类忘记调用cancle()方法
- 属性动画忘记调用cancle()方法
解决方法:调用对应的API释放资源
注册对象未销毁
- BroadcastReceiver
- EventBus
解决办法:调用反注册API
静态变量持有大数据对象
解决方法:尽量使用数据库,文件存储等方式存储数据
单例持有Context引起的内存泄漏
解决方法:优先使用Application的Context,如使用Activity的Context,最好使用弱引用封装。使用时如果获取不到,直接return
内部类引起的内存泄漏
非静态内部类和匿名内部类持有默认持有外部类,它们的生命周期比外部类长。外部类对象生命周期结束时,外部类对象仍被引用不能被回收。
解决方法分2种:
- Handle
- 静态内部类+弱引用 或者 在外部类定义为平级的类
- handler对象在对其引用的类生命周期结束时调用
removeCallbacks
方法
- Thread
容器对象没有清理造成的内存泄漏
解决方法:退出程序前需要将容器的内容清空,置为null,再退出
WebView引起的内存泄漏
解决方法:
- 销毁webview时使用如下代码实现
1
2
3
4
5
6
7
8
9
10
11
protected void onDestroy() {
super.onDestroy();
// 先从父控件中移除WebView
mWebViewContainer.removeView(mWebView);
mWebView.stopLoading();
mWebView.getSettings().setJavaScriptEnabled(false);
mWebView.clearHistory();
mWebView.removeAllViews();
mWebView.destroy();
} - 内存泄漏也可能是由于前端代码原因造成的,为WebView使用单独的进程,避免webview引起的内存泄漏导致应用的其他功能受到影响。
内存抖动
内存抖动导致OOM的原因
- 频繁创建对象,导致内存不足或碎片
- 不连续的内存片无法被分配,导致OOM
内存抖动复现方法
点击按钮使用handler发送一个空消息,handleMessage 接收到消息后创建内存抖动,即在 for 循环创建 100个容量为10万 的 strings 数组并在 30ms 后继续发送空消息。
一般使用 Memory Profiler (表现为 频繁GC、内存曲线呈锯齿状)结合代码排查即可找到内存抖动出现的地方。
通常的技巧就是着重查看 循环或频繁被调用 的地方。
内存抖动常见场景
字符串使用加号拼接
解决方法:
- 使用StringBuffer代替
- 初始化时设置容量,减少StringBuffer的扩容
资源复用
解决方法:
- 使用全局缓存池,以重用申请和释放的对象
- 注意结束使用后,需要手动释放对象池中的对象
减少不合理的对象创建
- onDraw、getView中创建的对象尽量复用
- 避免在循环中不断的创建局部变量
使用合理的数据结构
使用SparseArray类族、ArrayMap来替代HashMap