概述

内存泄露是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. 设计并实现一个LRU Cache

    一.什么是Cache 1 概念 Cache,即高速缓存,是介于CPU和内存之间的高速小容量存储器.在金字塔式存储体系中它位于自顶向下的第二层,仅次于CPU寄存器.其容量远小于内存,但速度却可以接近CP ...

  2. Java内存问题的一些见解

    在Java中,内存泄露和其它内存相关问题在性能和可扩展性方面表现的最为突出.我们有充分的理由去具体地讨论他们. Java内存模型--或者更确切的说垃圾回收器--已经攻克了很多内存问题. 然而同一时候, ...

  3. placeholder 占位符

    placeholder 简介  |  TensorFlow https://tensorflow.google.cn/programmers_guide/low_level_intro 供给 目前来讲 ...

  4. 【LIS】Luogu P1020 导弹拦截

    昨天晚上看蓝书,看到了LIS问题的优化解法. 是比O(n方)更快的解法,实际上是一个常数优化. 先讲一下朴素的解法: 一个集合a,a[i]是第i个元素.设dp[i]为以编号为i的元素结尾的最长不上升子 ...

  5. 呐喊-Skrik

    尼斯,1892年1月22日,我和两个朋友还在散步,太阳已快下山了,天空突然间变得血一样红,我似乎感受到了一种悲伤忧郁的气息,我止住了脚步,轻轻地倚在篱笆边,极度的疲倦已使我快要窒息了.火焰般的云彩像血 ...

  6. zabbix如何添加主机监控

    1,首先,监控的主机安装zabbix客户端.zabbix提供多种监控方式,我们这里监控的主机上边安装agentd守护端进行数据收集并监测. 其中客户端安装我们这里就不介绍了,请参考之前教程里边的客户端 ...

  7. MongoDB全文搜索——目前尚不支持针对特定field的搜索

    > db.articles.createIndex( { subject: "text" } ) { "createdCollectionAutomatically ...

  8. window安装Elasticsearch

    下载,https://www.elastic.co/cn/downloads/elasticsearch 下载后解压,进入解压目录,运行./elasticsearch.bat 运行成功如下 (运行需要 ...

  9. openstack dnsmasq彭祖

    Openstack dnsmasq配置域名解析,openstackdnsmasq vi /etc/nova/nova.conf 在[DEFAULT]添加 dnsmasq_config_file=/et ...

  10. http-2.2

    HTTP-2.2 httpd 配置文件的组成: grep "Section" /etc/httpd/conf/httpd.conf ### Section 1: Global En ...