所谓内存泄漏,是指本该被回收的内存由于某种原因绕开了GC回收算法,从而导致该内存无法被有效数据使用而使得总内存减小的情况。

内存泄漏会导致内存消耗的增加,大量的消耗会使得APP OOM,特别是在一些内存比较小的机器上。下面我们看看有哪些工具可以用来分析内存泄漏。

Heap Dump

Heap Dump的主要功能就是查看不同的数据类型在内存中的使用情况。它可以帮助你找到大对象,也可以通过数据的变化发现内存泄漏。

使用Heap Dump

打开Android Device Monitor工具,在左边Devices列表中选择要查看的应用程序进程,点击Update Heap按钮,在右边选择Heap选项,并点击Cause GC按钮,就会开始显示数据。我们每次点击Cause GC按钮都会强制应用程序进行垃圾回收,并将清理后的数据显示在Heap工具中。如下图所示。

从上图可以看出,Heap工具共有三个区域,分别是总览视图(上部)、详情视图(中部)和内存分配柱状图(下部)。

总览视图

其中总览视图可以查看整体的内存情况,表中的显示信息如下所示:

  • Heap Size 堆栈分配给该应用程序的内存大小
  • Allocated 已使用的内存大小
  • Free 空闲的内存大小
  • %Used 当前Heap的使用率(Allocated/Heap Size)
  • #Objects 对象的数量

详情视图

详细视图展示了所有的数据类型的内存情况,表中列的信息如下所示:

  • Total Size 总共占用的内存大小
  • Smallest 将该数据类型的对象从小到大排列,排在第一个的对象所占用的内存
  • Largest 将该数据类型的对象从小到大排列,排在最后一个的对象所占用的内存
  • Median 将该数据类型的对象从小到大排列,排在中间的对象所占用的内存
  • Average 该数据类型的对象所占用内存的平均值

除了列的信息,还有行信息:

  • data object 对象
  • class object 类
  • 1-byte array (byte[],boolean[]) 1字节的数组对象
  • 2-byte array (short[],char[]) 2字节的数组对象
  • 4-byte array (object[],int[],float[]) 4字节的数组对象
  • 6-byte array (long[],double[]) 8字节的数组对象
  • non-Java object 非Java对象

行信息中两个比较重要的参数:
free—它与总览视图中的free的含义不同,它代表内存碎片。当新创建一个对象时,如果碎片内存能容下该对象,则复用碎片内存,否则就会从free空间(总览视图中的free)重新划分内存给这个新对象。free是判断内存碎片化程度的一个重要的指标。
1-byte array—图片是以byte[]的形式存储在内存中的,如果1-byte array一行的数据过大,则需要检查图片的内存管理了。

检测内存泄漏

我们先写一个内存泄漏的例子:
MainActivity:

public class MainActivity extends AppCompatActivity {private Button button;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);button =(Button)findViewById(R.id.bt_next);button.setText("SecondActivity");button.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {startActivity(new Intent(MainActivity.this,SecondActivity.class));}});}
}

SecondActivity:

public class SecondActivity extends AppCompatActivity {private static Object inner;private Button button;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);button = (Button) findViewById(R.id.bt_next);button.setText("MainActivity");button.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {createInnerClass();finish();}});}class InnerClass {}private void createInnerClass() {inner = new InnerClass();}
}

内存泄漏的原因很简单,SecondActivity的内部类InnerClass Hold住了外部类实例的引用,而InnerClass的实例是静态的,就会间接的长期维持着外部类实例的引用,阻止被系统回收,导致SecondActivity实例不能被释放。

Heap Dump检测内存泄漏:通常做法是使用Update Heap进行内存监听,然后操作可能发生泄漏的APP功能、界面,并点击Cause GC进行手动GC,经过多次操作后查看data object的Total Size大小是否有很大的变化,如果有则可能发生了内存泄漏,导致内存使用不断增大。

步骤:
(1)在左边Devices列表中选择要查看的应用程序进程,点击Update Heap按钮,在右边选择Heap选项,并点击Cause GC按钮,就会开始显示数据,如下图所示。

(2)在MainActivity和SecondActivity间跳转多次。这样会生成多个SecondActivity实例且不能释放。重新点击Update Heap和Cause GC按钮,显示新的数据。

可以看到data object由610.500KB增长到1.158MB

(3)这时我点击Cause GC按钮,数据显示为:

经过Cause GC的操作,Total Size的值从1.158MB变为了667.109KB,这是一个比较大的变化,说明在Cause GC操作之前有518.683KB(1.158MB-667.109KB)的内存没有被回收,可能发生了内存泄漏。你多GC几次,甚至会释放更多的内存。

Allocation Tracker

使用Heap Dump可以让你对APP的内存整体使用情况进行掌控,但缺点是无法了解每块内存具体分配给哪个对象了,这时就需要使用Allocation Tracker工具来进行内存跟踪。它允许你在执行某些操作的同时监视在何处分配对象,了解这些分配使你能够调整与这些操作相关的方法调用,以优化应用程序性能和内存使用。

Allocation Tracker能够做到如下的事情:

  • 显示代码分配对象类型、大小、分配线程和堆栈跟踪的时间和位置。
  • 通过重复的分配/释放模式帮助识别内存变化。
  • 当与 HPROF Viewer结合使用时,可以帮助你跟踪内存泄漏。例如,如果你在堆上看到一个bitmap对象,你可以使用Allocation Tracker来找到其分配的位置。

使用Allocation Tracker

AS和DDMS中都有Allocation Tracker,这里会介绍AS中的Allocation Tracke如何使用。
使用的步骤为:

  • 运行需要监控的应用程序。
  • 点击AS面板下面的Android Monitors选项,查看
  • 点击Start Allocation Tracking按钮
  • 操作应用程序。
  • 点击Stop Allocation Tracking按钮 ,结束快照。这时Memory Monitor会显示出捕获快照的期间,如下图所示。

  • 过几秒后就会自动打开一个窗口,显示当前生成的alloc文件的内存数据。

alloc文件分析

该alloc文件显示以下信息:

  • Method—负责分配的Java方法
  • Count—分配的实例总数
  • Total Size—分配内存的总字节数

目前的菜单选项是Group by Method我们也可以选择 Group By Allocator,如下图所示

同样是上面的demo,我们在MainActivity和SecondActivity间跳转了5次。
可以看到SecondActivity生成了5个匿名内部类OnClickListener实例(SecondActivity$1 表示它的第一个匿名内部类)和5个内部类InnerClass的实例,每个实例16个字节,且都没有被释放内存。

我们可以选择列表中的一项,单击鼠标右键,在弹出的菜单中选择jump to the source就可以跳转到对应的源文件中。
除此之外,还可以点击Show/Hide Chart按钮来显示数据的图形化,如下图所示。

MAT

如果想要深入的进行分析并确定内存泄漏,就要分析疑似发生内存泄漏时所生成堆存储文件。堆存储文件可以使用DDMS或者Memory Monitor来生成,输出的文件格式为hprof,而MAT就是来分析堆存储文件的。

MAT,全称为Memory Analysis Tool,是对内存进行详细分析的工具,它是Eclipse的插件,如果用Android Studio进行开发则需要单独下载它,下载地址为:http://eclipse.org/mat/。

生成hprof文件

我们这里分析一下用AS的Memory Monitor来生成hprof文件。

我们还是先写一个内存泄漏的例子:

public class MainActivity extends AppCompatActivity {private Button button;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);button =(Button)findViewById(R.id.bt_next);button.setText("SecondActivity");button.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {startActivity(new Intent(MainActivity.this,SecondActivity.class));}});}
}
public class SecondActivity extends AppCompatActivity {private Button button;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);button = (Button) findViewById(R.id.bt_next);button.setText("MainActivity");button.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {LeakThread leakThread = new LeakThread();leakThread.start();finish();}});}class LeakThread extends Thread {@Overridepublic void run() {try {Thread.sleep(60 * 60 * 1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}

内存泄漏的原因是非静态内部类LeakThread Hold住了外部类的引用,而LeakThread中做了耗时操作,导致外部类SecondActivity无法被释放。

生成hprof文件主要分为一下几个步骤:

  • 运行需要监控的应用程序。
  • 点击AS面板下面的Android Monitors选项,查看
  • 操作应用程序(本文的例子就是不断切换Activity)。
  • 点击Dump Java Heap按钮 ,生成hprof文件。

Memory Monitor生成的hprof文件不是标准的,AS提供了便捷的转换方式:Memory Monitor生成的hprof文件都会显示在AS左侧的Captures标签中,在Captures标签中选择要转换的hprof文件,并点击鼠标右键,在弹出的菜单中选择Export to standard.hprof选项,即可导出标准的hprof文件,如下图所示。

MAT分析hpof文件

用MAT打开标准的hprof文件,选择Leak Suspects Report选项。这时MAT就会生成报告,这个报告分为两个标签页,一个是Overview,一个是Leak Suspects,如下图所示。

Leak Suspects中会给出了MAT认为可能出现内存泄漏问题的地方,本例共给出了4个内存泄漏猜想,通过点击每个内存泄漏猜想的Details可以看到更深入的分析清理情况。如果内存泄漏不是特别的明显,通过Leak Suspects是很难发现内存泄漏的位置。

打开Overview标签页,首先看到的是一个饼状图,它主要用来显示内存的消耗,饼状图的彩色区域代表被分配的内存,灰色区域的则是空闲内存,点击每个彩色区域可以看到这块区域的详细信息,如下图所示。

再往下看,Actions一栏的下面列出了MAT提供的四种Action,其中分析内存泄漏最常用的就是Histogram和Dominator Tree。Histogram可以统计内存中对象的名称、种类、实例数和大小,而Dominator Tree则是建立这些内存对象之间的关系。
我们点击Actions中给出的链接或者在MAT工具栏中就可以打开Histogram和Dorminator Tree。

Histogram

图中可以看出Dorminator Tree有四列数据。

  • Class Name:类名
  • Objects:对象实例的个数
  • Shallow Heap:对象自身占用的内存大小,不包括它引用的对象。如果是数组类型的对象,它的大小是数组元素的类型和数组长度决定。如果是非数组类型的对象,它的大小由其成员变量的数量和类型决定。
  • Retained Heap:一个对象的Retained Set所包含对象所占内存的总大小。换句话说,Retained Heap就是当前对象被GC后,从Heap上总共能释放掉的内存。

在列表顶部的Regex区域,可以输入过滤条件(支持正则表达式),通常Activity的内存泄漏,可以直接通过输入Activity名获取与之相关的的实例。

可以看到,SecondActivity实例创建了11次,基本可以判断内存泄漏了。具体是如何泄漏的呢?可以通过查看GC对象的引用链来分析。在SecondActivity上右键,选择Merge Shortest Paths to GC Root,并通过弹出的列表选择相关类型的引用(强、软、弱、虚),分析不同引用类型下的GC情况,这里我们选择exclude all phantom/weak/soft etc. references,因为这个选项排除了虚引用、弱引用和软引用,这些引用一般是可以被回收的。这时MAT就会给出SecondActivity的GC引用链。

到这里整个内存泄漏一目了然了,引用SecondActivity的是内部类LeakThread,this$0的含义就是内部类自动保留的一个指向所在外部类的引用,而这个外部类就是SecondActivity,导致SecondActivity无法被GC。

同时,在Histogram中还可以查看一个对象包含了哪些对象的引用。例如查看SecondActivity包含的引用,在SecondActivity上右键,选择List objects—with incoming references(显示选中对象被哪些外部对象引用,而with outcoming references表示选中对象持有哪些对象的引用)

Dominator Tree

Dorminator Tree意味支配树,从名称就可以看出Dorminator Tree更善于去分析对象的引用关系。而Histogram更侧重于量的分析。

Shallow Heap和Retained Heap的含义和上面Histogram中的一样。

同样过滤SecondActivity:

发现有些图标带有小圆点,表示它们可以被GC系统访问到,是内存泄漏的重点怀疑对象。那么SecondActivity没有原点,是不是代表不能被GC访问,可以回收呢?当然不是,如果可以回收,又怎么会存在这么多的实例呢。那怎么找到它的GC Root呢?在SecondActivity上右键,选择Path To GC Roots,同样选择exclude all phantom/weak/soft etc. references

得出的结果和上面是一样的,引用SecondActivity的是LeakThread,这导致了SecondActivity无法被GC。

OQL

OQL全称为Object Query Language,类似于SQL语句的查询语言,能够用来查询当前内存中满足指定条件的所有的对象。它的查询语句的基本格式为:

SELECT * FROM [ INSTANCEOF ]    <class_name> [ WHERE <filter-expression>]

当我们点击OQL按钮,输入条件select * from instanceof android.app.Activity并按下F5时(或者按下工具栏的红色叹号),会将当前内存中所有Activity都显示出来,如下图所示。

更过用法详见官方文档。

LeakCanary

LeakCanary 是一个开源的在debug版本中检测内存泄漏的java库。

使用LeakCanary

在APP的build.gradle文件添加:

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

接下来在Application加入如下代码

public class BaseApplication extends Application {@Override public void onCreate() {super.onCreate();//如果当前的进程是用来给LeakCanary进行堆分析的则returnif (LeakCanary.isInAnalyzerProcess(this)) {return;}LeakCanary.install(this);}
}

上面代码只能够检测Activity的内存泄漏,当然还存在其他类的内存泄漏,这时我们就需要使用RefWatcher来进行监控。改写Application,如下所示

public class BaseApplication extends Application {private RefWatcher refWatcher;@Override public void onCreate() {super.onCreate();refWatcher= setupLeakCanary();}private RefWatcher setupLeakCanary() {//如果当前的进程是用来给LeakCanary进行堆分析的则returnif (LeakCanary.isInAnalyzerProcess(this)) {return RefWatcher.DISABLED;}return LeakCanary.install(this);}public static RefWatcher getRefWatcher(Context context) {BaseApplication baseApplication = (BaseApplication) context.getApplicationContext();return baseApplication.refWatcher;}
}

这里我们仍然使用上一节的demo,只是在SecondActivity中实现onDestroy方法,其中得到RefWatcher,并调用它的watch方法,watch方法的参数就是要监控的对象。

public class SecondActivity extends AppCompatActivity {private Button button;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);button = (Button) findViewById(R.id.bt_next);button.setText("MainActivity");button.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {LeakThread leakThread = new LeakThread();leakThread.start();finish();}});}class LeakThread extends Thread {@Overridepublic void run() {try {Thread.sleep(60 * 60 * 1000);} catch (InterruptedException e) {e.printStackTrace();}}}@Overrideprotected void onDestroy() {super.onDestroy();RefWatcher refWatcher = BaseApplication.getRefWatcher(this);refWatcher.watch(this);}
}

其实这个例子中onDestroy方法是多余的,因为LeakCanary在调用install方法时会启动一个ActivityRefWatcher类,它用于自动监控Activity执行onDestroy方法之后是否发生内存泄露。这里只是为了方便举例,如果想要监控Fragment,在Fragment中添加如上的onDestroy方法是有用的。

操作

运行程序,这时会在界面生成一个名为Leaks的应用图标。接下来不断的切换Activity,这时会闪出一个提示框,提示内容为:“Dumping memory app will freeze.Brrrr.”。再稍等片刻,内存泄漏信息就会通过Notification展示出来

Notification中提示了MainActivity发生了内存泄漏, 泄漏的内存为4.3KB。点击Notification就可以进入内存泄漏详细页,除此之外也可以通过Leaks应用的列表界面进入,列表界面如下图所示

点击加号就可以查看具体类所在的包名称。整个详情就是一个引用链:SecondActiviy的内部类LeakThread引用了LeakThread的this0,this0,this0的含义就是内部类自动保留的一个指向所在外部类的引用,而这个外部类就是详情最后一行所给出的SecondActiviy的实例,这将会导致SecondActiviy无法被GC,从而产生内存泄漏。

除此之外,我们还可以将 heap dump(hprof文件)和info信息分享出去,如下图所示。

需要注意的是分享出去的hprof文件并不是标准的hprof文件,还需要将它转换为标准的hprof文件,这样才会被MAT识别从而进行分析。

Android 内存检测工具相关推荐

  1. Android内存检测工具系列工具集

    Android内存检测工具系列工具集 Android关于内存的工具不少,灵活地选择工具就显得特别重要.在此特别推荐分享涵盖一定初步和定位能力的工具,可以让我们一步到位地分析问题,提升效率. 在此列举几 ...

  2. Android内存检测工具

    什么是内存泄漏? 在运行的程序中,如果一个无法访问的对象仍然占用着内存空间,即为此对象造成了内存泄漏 垃圾回收(GC)机制: 当垃圾回收运行时,虚拟机首先会识别GC Root.GC Root 是一个可 ...

  3. 内存检测工具Dr. Memory的使用

    Dr. Memory是一个内存调试工具,它是一个开源免费的内存检测工具,它能够及时发现内存相关的编程错误,比如未初始化访问.内存非法访问.数组越界读/写.以及内存泄露等.它可以在Linux.Windo ...

  4. 【调试】Linux下超强内存检测工具Valgrind

    [调试]Linux下超强内存检测工具Valgrind 内容简介 Valgrind是什么? Valgrind的使用 Valgrind详细教程 1. Valgrind是什么? Valgrind是一套Lin ...

  5. 动态内存检测工具Valgrind

    1. Valgrind查找内存泄露利器 Valgrind是一个GPL的软件,用于Linux(For x86, amd64 and ppc32)程序的内存调试和代码剖析.你可以在它的环境中运行你的程序来 ...

  6. 台式机内存测试软件中文版,内存检测工具 MemTest

    MemTest是可靠的内存检测工具,通过对电脑进行储存与读取操作来分析检查内存情况.内存检测工具 MemTest不但可以彻底的检测出内存的稳定度,还可同时测试记忆的储存与检索资料的能力,让你可以确实掌 ...

  7. 如何查看计算机硬盘的软件内存条,内存检测工具,内存检测软件 - 内存条检测工具就用金山卫士 - 安全专题...

    在去电脑城去买内存条,不知道该内存条怎么样,怕自己遇到不法奸商.金山卫士已经集成 内存条检测工具,可以智能监测到该内存所有信息以及该内存性能怎么样. 内存检测工具 就用金山卫士 许多网民从电脑城购买回 ...

  8. ios代码中的内存泄露,内存检测工具leaks 检测不出来

    iphone开发过程中,代码中的内存泄露我们很容易用内存检测工具leaks 检测出来,并一一改之,但有些是因为ios 的缺陷和用法上的错误,leaks 检测工具并不能检测出来,你只会看到大量的内存被使 ...

  9. 内存测试内存检测工具

    测试内存,往往不局限于一种软件,因为每种工具都有自己的局限性.灵活运用多种工具,可以实现效益最大化 1.<HCI MemTest> https://hcidesign.com/memtes ...

最新文章

  1. python 安装nameerror_python NameError:name’file’未定义
  2. Linux段式管理与页式管理
  3. composer(作曲家)安装php-ml
  4. 【剑指offer】面试题57 - II:和为s的连续正数序列(Java)
  5. nginx https透明代理_nginx正向https代理配置
  6. Virtio: An I/O virtualization framework for Linux | 原文
  7. jmeter一个线程组多个请求_Jmeter模拟真实用户压测场景之阶梯螺纹线程组、终极线程组、并发线程组实例...
  8. 实例讲解朴素贝叶斯分类器
  9. linux离线安装系统工具arping
  10. Android10支持dcip3,dcip3 相当于多少srgb
  11. android动态权限依赖库,动态申请app权限:郭霖大神的PermissionX库带你告别原生
  12. 产业新基建,撬动数字经济发展新机遇
  13. linux查询日志命令加过滤,日志查看技巧之筛选[linux命令集][排查篇]
  14. unity-shader-2
  15. N76E003 管脚中断
  16. mysqldump备份恢复数据库
  17. 节点操作-创建createElement||appendChild-/追加-/插入insertBefore-/替换replaceChild-/删除remove-克隆元素 cloneNode(增删改查)
  18. 微信漫画小程序全开源商业版
  19. 2.Seq2Seq注意力机制
  20. MacW资讯:如何在Final Cut Pro X中安裝fcpx模板

热门文章

  1. 《白鹿原》电视剧观后感
  2. pptx---基础概念解释
  3. 程慧:积极建旅游大数据 科学监管市场
  4. SpringBoot25-spingboot数据访问-数据缓存Cache
  5. [Usaco2008 Mar]The Loathesome Hay Baler麻烦的干草打包机
  6. 什么是feature map
  7. 分类评价指标 F值 详解 | Micro F1 Macro F1 Weight F1
  8. Ubuntu更新卡在 flashplugin-installer
  9. 操作系统的概念 (OS学习笔记)
  10. 团队负责人(team leader)职责