概述

内存泄露是Android开发中比较常见的问题,一旦发生会导致大量内存空间得不到释放,可用内存急剧减少,导致运行卡顿,部分功能不可用甚至引发应用crash。对于复杂度比较高、多人协同开发的项目来讲,如何快速排查并解决内存泄露问题,往往是一个很棘手的问题,也是作为一名高级Android工程的基本技能。本文旨在简单介绍内存泄漏产生的原因,总结Android中常见的内存泄漏,重点介绍如何使用工具快速排查并解决此类问题。

Android常见内存泄露分析

Java作为一种高级语言,内存管理的任务大部分由JVM自动完成,开发者只需要遵守一定的编程规范就可以避免绝大多数问题。由于对象创建时分配内存和对象销毁时回收内存都交给JVM来处理,正常情况下当我们需要销毁一个对象时只需要消除对它的引用就可以了,JVM会在GC时把它所占用的内存自动交还给系统。但如果我们在程序中错误的持有了多余的引用,超过了其正常的使用范围,就会导致该对象以及其引用的对象无法及时释放。最极端的情况是这个对象被声明为static或者被应用的Application引用,导致其存活的时间与整个应用的生命周期相同,这样便产生了内存泄漏。

网上对于内存泄露原理的介绍比较多,这里不做过多介绍,放一篇帖讲解的比较全面,供大家参考:内存泄漏全解析,从此拒绝ANR,让OOM远离你的身边,跟内存泄漏say byebye。结合作者观点,稍做总结,括号中是正确的做法:

  1. 单例模式中错误引用Activity作为Context(应该使用ApplicationContext
  2. 使用非静态类Handler并且未及时移除message(使用静态Handler与WeakReference,退出Activity时及时remove messages
  3. 使用匿名类/非静态内部类持有外部对象引用(尽量使用private static class作为内部类
  4. 集合对象未及时清理
  5. WebView引发的内存泄露(退出Activity时及时销毁WebView
  6. ListView的Adapter中创建ItemView时未使用缓存(使用convertView和静态Holder缓存
  7. 对象的注册与反注册没有成对出现造成的内存泄露(注册与反注册一定要成对出现

工具

虽然有上面这些原则,但是开发过程中我们更多需要的是能利用工具立刻定位出问题出现的,而不是去逐行审查代码。下面介绍一些用来快速排查内存泄露问题的工具,并演示如何结合AndroidStudio开发环境使用这工具。

LeakCanary

LeakCanary是一个专门用来检测内存泄露的组件,只需要简单的配置就可以集成到项目中,在程序运行时能够自动dump内存并且生成报告。

首先我们在build.gradle中添加如下依赖:

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'
}

然后在Application的onCreate()方法中对LeakCanary进行初始化:

 public class LeakApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
return;
}
LeakCanary.install(this);
}
}

这样就完成了LeakCanary的配置。接下来我们模拟一个常见的错误,用来演示LeakCanary的用法。首先写一个错误实现的单例模式:

 public class SingletonBad {
private static final String TAG = "Singleton";
private static SingletonBad instance; private final Context mContext; private SingletonBad(Context mContext) {
this.mContext = mContext;
} public static SingletonBad getInstance(Context context) {
if (instance == null) {
synchronized (SingletonBad.class) {
if (instance == null) {
instance = new SingletonBad(context);
}
}
}
return instance;
} public void doSomeThing() {
Log.d(TAG, "do something");
}
}

在MainActivity中有一个Button,点击后打开SecondActivity:

    <Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="startSecond"
android:text="start"/>
public class MainActivity extends AppCompatActivity {

    @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
} public void startSecond(View view) {
Intent intent = new Intent(this, SecondActivity.class);
startActivity(intent);
}
}

SecondActivity中简单调用了SingletonBad的doSomething()方法。

public class SecondActivity extends AppCompatActivity {

    @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
SingletonBad.getInstance(this).doSomeThing();
}
}

好了,我们启动应用进入MainActivity,点击start按钮进入到SecondActivity,再退回到MainActivity。此时LeakCanary开始工作了,弹出了一个Dump memory的提示如下图,程序会稍微卡顿。

Dump分析完成后,LeakCanary发现SecondActivity被泄露了,会在通知栏中给出提示如下图:

点击这条通知,跳转到分析报告:

怎么样,报告是不是很漂亮!我们可以清楚的看到,SingletonBad类中的静态变量instance持有了SecondActivity作为mContext,导致了内存泄露。有了LeakCanary帮我们做自动分析,内存泄露一目了然,省去了很多工作。然而LeakCanary并不是万能的,对于Activity的泄露基本都能及时发现,但是其他比较复杂的情况并不一定能分析出来,这个时候我们就需要用到MAT去做更具体的分析了。

MAT

MAT即Memory Analyzer,是一款专门用来分析内存对象的工具,可以集成到Eclipse中也能单独使用。由于现在Android开发基本都使用AndroidStudio,这里我们就使用了一个独立的版本。

首先我们先介绍一下AndroidStudio中的AndroidMonitor,一共有4项内容,分别是内存占用,cpu使用率,网络和GPU,我们今天关注的重点是第一项内存。红色框里的几个按钮我们需要用到,分别用来触发GC、Dump堆栈和跟踪内存分配。

我们继续使用上面的例子,为了让问题看起来更明显更接近真实情况,我们为SecondActivity添加一张背景图片。首先从MainActivity跳转到SecondActivity,然后按back键退回,这个时候的内存走势如下图,此时点击GC按钮清除掉没有引用的类,发现退回MainActivity后内存并没有下降。

然后点击Dump按钮,稍后便会生成一个dump文件,可以看到内存中保留的对象及其对应的count,size等,可以在此做简单的分析。

在内存中对象比较多的时候,AndroidStudio中做分析已经有些力不从心,这个时候就要我们的主角MAT登场了。

首先把前面生成的dump文件导出为一个标准的.hprof文件:在AndroidStudio的左侧边栏选择Captures选项,在Heap Snapshot下找到刚才生成的.hprof文件,右键选择“Export to standard .hprof”选项,选择保存位置即可。

然后我们打开MAT,File->Open->选择刚才保存的文件,看到如下界面:

上图中的饼图可以看出当前内存占用比例,其中Bitmap占用了28.6MB,明显是有问题的。我们点一下“Leak Suspects”按钮,生成下图:

再点击Details:

好了,现在我们能看到,是因为这个Bitmap被SecondActivity所引用,而SecondActivity又被SingletonBad引用导致,得出的结果跟我们的预期是一致的。

当然MAT中还有很多更强大的工具,比如我们可以点击“Dominator Tree”这个按钮,按照Retained Heap排序后,一个Bitmap对象排到了第一个位置。我们选中它,然后右键选中“Merge Shortest Paths to GC Roots” -> “exclude weak references”。

展开后便得到了下图:

对于前面这个操作稍作解释。我们知道JVM判断一个对象需要被GC的依据是这个对象没有路径可以通过强引用到达GC Root,通过上面这个操作我们去除了所有的weak reference,剩下的基本就是强引用了。注意最上面的SingletonBad对象左边的有一个黄色的点,表示这个对象是能够到达GC Root的,因此根据这个引用链我们可以看到,SecondActivity的背景图片最终被SingletonBad的instance引用,导致无法被回收,这便是内存泄露的根源所在。

如果对于前面的GC Root不理解的,可以去看郭霖大神这篇帖子,里面有比较详细的讲解。

本篇到这里就结束了,内存泄露是一种比较常见又不容易解决的问题,文中的例子都比较简单,实际遇到时情况可能会比这些复杂很多,但是有了这些工具的帮助,相信我们能更快的定位和处理这些问题。

如何快速排查解决Android中的内存泄露问题的更多相关文章

  1. 查找并修复Android中的内存泄露—OutOfMemoryError

    [编者按]本文作者为来自南非约翰内斯堡的女程序员 Rebecca Franks,Rebecca 热衷于安卓开发,拥有4年安卓应用开发经验.有点完美主义者,喜爱美食. 本文系国内ITOM管理平台 One ...

  2. Android内存优化8 内存检测工具2 LeakCanary——直白的展现Android中的内存泄露

    之前碰到的OOM问题,终于很直白的呈现在我的眼前:我尝试了MAT,但是发现不怎么会用.直到今天终于发现了这个新工具: 当我们的App中存在内存泄露时会在通知栏弹出通知: 当点击该通知时,会跳转到具体的 ...

  3. LeakCanary——直白的展现Android中的内存泄露

    之前碰到的OOM问题,终于很直白的呈现在我的眼前:我尝试了MAT,但是发现不怎么会用.直到今天终于发现了这个新工具: 当我们的App中存在内存泄露时会在通知栏弹出通知: 当点击该通知时,会跳转到具体的 ...

  4. 56、LeakCanary——直白的展现Android中的内存泄露

    转载:http://blog.csdn.net/watermusicyes/article/details/46333925 DEMO下载地址:https://github.com/SOFTPOWER ...

  5. LeakCanary Android 和 Java 内存泄露检测

    说起内存泄漏还是挺让人头疼的,而且不是每个手机都会发生的情况,往往又不易察觉,那么今天我们就来介绍下LeakCanary这个工具 githup:https://github.com/square/le ...

  6. 系统剖析Android中的内存泄漏

    [转发]作为Android开发人员,我们或多或少都听说过内存泄漏.那么何为内存泄漏,Android中的内存泄漏又是什么样子的呢,本文将简单概括的进行一些总结. 关于内存泄露的定义,我可以理解成这样 没 ...

  7. Android中的内存管理机制以及正确的使用方式

    概述 从操作系统的角度来说,内存就是一块数据存储区域,属于可被操作系统调度的资源.现代多任务(进程)的操作系统中,内存管理尤为重要,操作系统需要为每一个进程合理的分配内存资源,所以可以从两方面来理解操 ...

  8. 解决Android中No resource found that matches android:TextAppearance.Material.Widget.Button.Inverse问题

    解决Android中No resource found that matches android:TextAppearance.Material.Widget.Button.Inverse问题http ...

  9. Android DDMS检测内存泄露

    Android DDMS检测内存泄露 DDMS是Android开发包中自带工具,可以测试app性能,用于发现内存问题. 1.环境搭建 参考之前发的Android测试环境搭建相关文章,这里不再复述: 2 ...

随机推荐

  1. 七牛云上传图片到cdn,cdn返回的是一个只有图片的url

    如:

  2. java编程思想-复用类(2)

    如果java的基类拥有某个已被多次重载的方法名称,那么在导出类中重新定义该方法名称并不会屏蔽其在基类中的任何版本(这一点与C++不同) class Homer { char doh(char c) { ...

  3. [Maid] Write Tasks in Markdown with Maid

    Maid enables you to write your tasks in Markdown. Create a maidfile.md or a README.mdthen add Header ...

  4. HDU 1030 数学题

    给出两点,求这两点在图上的最短路径 分别以最上,左下,右下为顶点,看这个三角图形 ans=这三种情况下两点的层数差 #include "stdio.h" #include &quo ...

  5. Js、Jquery对goTop功能的实现

    本文介绍用原生JS和Jquery分别实现的网页goTopbutton功能,以及在这个过程中碰到的问题. 终于实现的效果类似:http://sc2.163.com/home(注意右下角的top) 代码: ...

  6. hbase shell经常使用命令

    hbase经常使用命令 /usr/local/cloud/hbase/bin/hbase shell 用shell来连接hbase exit 退出hbase shell version 查看hbase ...

  7. UILongPressGestureRecognizer 运行两次的解决的方法

    近期维护之前用iOS SDK 3.2写过的3年多前的map方面的模块,在地图上长按pin,发觉一个点莫名奇异点插了两个pin. 查了一下,原来是如今的sdk要在UILongPressGestureRe ...

  8. struts2基础代码实现

    结构图: load.jsp <%@ page language="java" import="java.util.*" pageEncoding=&quo ...

  9. [Codeforces 140C] New Year Snowmen

    [题目链接] https://codeforces.com/problemset/problem/140/C [算法] 显然 , 我们每次应优先考虑数量多的雪球 将雪球个数加入堆中 , 每次取出数量前 ...

  10. Python不兼容问题

    今天遇到了一个Python2与3不兼容的坑. ride是基于robot框架的python自动化ui,但它只支持python2,而我电脑环境只有python3,想跑别人基于ride编写的测试用例,折腾了 ...