公司相关项目须要进行内存优化。所以整理了一些分析内存泄漏的知识以及工作分析过程。

本文中不会刻意的编写一个内存泄漏的程序,然后利用工具去分析它。而是通过介绍相关概念,来分析怎样寻找内存泄漏。并附上自己的项目实战过程。

撰写过程中,本人深感JVM、操作系统相关知识了解不够深刻。不足之处非常欢迎指正说明。

内存泄漏基本概念

内存检測这部分,相关的知识有JVM虚拟机垃圾收集机制,类载入机制。内存模型,以及操作系统的基础知识(所以不要说JVM有啥用。操作系统有啥用啦 :) )。

编写没有内存泄漏的程序。对提高程序稳定性,提高用户体验具有重要的意义。同一时候,也是java程序猿进阶的重要内容。利用java编敲代码的时候,要特别注意内存泄漏相关的问题。尽管JVM提供了自己主动垃圾回收机制,可是还是有非常多情况会导致内存泄漏。

内存泄漏主要原因就是一个生命周期长的对象,持有了一个生命周期短的对象的引用。这样,会导致短的对象在该回收时候无法被回收。

Android中比較典型的有:

1、静态变量持有Activity的context。

2、或者Handler持有某个组件的context,同一时候假设Looper的消息队列中有针对该Handler的消息没有被处理。那么会被作为target持有强引用。终于的导致context无法释放,导致对应组件在退出时无法被内存回收。

3、非静态内部类默认持有外部类的引用。

有时候为了方便,我们会在Activity中定义一个Thread内部类,同一时候直接通过new Thread的方式去执行线程,那么在线程执行结束之前,线程都会持有Activity的引用,从而导致Activity无法被释放。


内存检測工具

LeakCananry

使用步骤

LeakCanary,主要监測的是使用过程中Activity,Fragment等组件是否没被内存回收。

用法也十分简单,相当于装了一个监听器,然后通过正常 操作去寻找内存泄漏,发生内存泄漏的时候会有Toast。同一时候能够在对应程序查看哪里发生内存泄漏。

方法比較简单。详细步骤能够查阅官方github。

增加leakcanary依赖以后。新建一个Application入口,在Oncreate方法中安装Leakcanary就可以。

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveXkyNTQxMTc0NDA=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="这里写图片描写叙述" title="">

当发生内存泄漏时,屏幕会出现Toast,同一时候打开桌面上的Leaks程序,显示泄漏的内存,例如以下图:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveXkyNTQxMTc0NDA=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="这里写图片描写叙述" title="">

总体流程

LeakCananry实现步骤大致是:

实现大致步骤是:

1、自己主动把activity增加到KeyedWeakReference

2、在background线程中,检查onDestroy后reference是否被清除。且没有触发gc

3、假设reference没有被清除,则dump heap到一个hprof文件并保存到app文件系统中

4、在一个单独进程中启动HeapAnalyzerService。HeapAnalyzer使用HAHA来分析heap dump。

5、HeapAnalyzer在heap dump中依据reference key找到KeyedWeakReference。

6、HeapAnalyzer计算出到GC Roots的最短强引用路径来推断是否存在泄露,然后build出造成这个泄露的引用链。

7、结果被传回来app进程的DisplayLeakService,并展示一个泄露的notification。

结论

方法的长处是简单易行。可是仅仅能检測Activity、Fragment是否发生内存泄漏。 对于一些项目比方sdk开发。非常可能整个程序没有一个Activity,所以这样的方式就不是非常有用。


观看总体内存使用情况

详情參见官方文档:

https://developer.android.com/studio/profile/investigate-ram.html#ViewingAllocations

使用adb shell。进入手机adb。执行命令:

dumpsys meminfo <包名> [-參数]

能够查看应用不同部分内存分配情况。

比方Java heap,Native heap等

输出是眼下详细应用的内存分配,单位是kilobytes

由于程序涉及jni,常常会分配本地内存。所以会使用adb shell 的方式去查看native heap的分配情况。

结果例如以下:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveXkyNTQxMTc0NDA=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="这里写图片描写叙述" title="">

分析各个參数:

Private Clean/Dirty RAM:

这部分内存是app的私有内存,当app销毁是操作系统能够回收到全部的内存。其中private dirty仅仅能被你的进程使用,同一时候仅仅能存在在内存其中,当内存不够。也不能通过分页技术存储到硬盘(操作系统相关知识),dalvik和native heap上的分配都是private dirty RAM。 Dirty RAM是内存中被改动过的页面,而Clean RAM是从持久文件(比方代码执行文件)映射出的内存。

PSS Total:

我们知道,进程之间彼此通信底层通过Binder Driver,通过操控一块共享内存进行读写来相互通信。

这样一来,为了进程间通信,Binder会为每一个进程在共享内存中开辟一块空间。

PSS的部分,包含了每一个进程的共享内存。

比如,一个内存页面被两个进程共享,那么页面大小的一半会被加到两个进程各自的PSS中。

通过累加全部进程的PSS,我们能够查看整个系统的内存使用情况。其实,PSS是衡量 (实际)使用内存的重要标准。

Dalvik Heap:

该字段衡量的是Dalvik虚拟机上堆分配情况,也就是我们在Java中使用new生命对象分配的内存。

列中PSS Total包含了和其他Zygote进程共享的内存(全部app进程都是从Zygote中fork出来的,都有一部分内存共享)。而Private Dirty则是app进程本身所使用的的内存。

.so mmap / .dex mmap

这部分主要指的是本地代码(so)和Davlik 虚拟机代码(dex)的代码大小。

PSS Total列中指的是包含android平台的代码,而private clean仅仅是程序本身执行的代码。

上面參数非常多,理解相关知识须要掌握操作系统内存部分。我们在測试的使用。普通情况下,我们关注private Dirty或者pss Total就能够查看app内存总体趋势。


DDMS

使用流程

  1. 启动eclipse后,切换到DDMS透视图。并确认Devices视图、Heap视图都是打开的;
  2. 将手机通过USB链接至电脑,链接时须要确认手机是处于“USB调试”模式,而不是作为“MassStorage”;
  3. 链接成功后,在DDMS的Devices视图中将会显示手机设备的序列号。以及设备中正在执行的部分进程信息;
  4. 点击选中想要监測的进程。比方system_process进程;
  5. 点击选中Devices视图界面中最上方一排图标中的“Update Heap”图标。
  6. 点击Heap视图中的“Cause GC”button;
  7. 此时在Heap视图中就会看到当前选中的进程的内存使用量的详细情况。

怎样检測内存泄漏?

Heap视图中部有一个Type叫做dataobject。即数据对象,也就是我们的程序中实例化的对象。

在data object一行中有一列是“Total Size”,其值就是当前进程中全部Java数据对象的内存总量,普通情况下,这个值的大小决定了是否会有内存泄漏。

正常情况下Total Size值都会稳定在一个有限的范围内,也就是说没有造成对象不被垃圾回收的情况。所以说尽管我们不断的操作会不断的生成非常多对象,而在虚拟机不断的进行GC的过程中。这些对象都被回收了。内存占用量会会落到一个稳定的水平。假设代码中存在没有释放对象引用的情况。则dataobject的Total Size值在每次GC后不会有明显的回落,随着操作次数的增多Total Size的值会越来越大

通过DDMS方式。DataObject 的totalSize假设稳定在一个大概范围内,则能够确定没有发生内存泄漏。


MAT

然而。并非全部的内存泄漏都十分明显,而且会终于导致OOM。有时候仅仅有几个对象被泄漏。尽管影响不大,可是无疑浪费了内存。

要发现这样的比較隐蔽的内存泄漏。我们须要使用MAT工具。

在了解MAT详细使用之前。要先了解一些相关概念。

支配树

支配树体现了对象实例间的支配关系,在对象引用图中,全部指向对象B的路径都经过对象A,则觉得对象A支配对象B。



在这张图里。左边是对象引用关系,对于A和B,要抵达这两个点必须经过GC root。

而对于C能够从A也能够从B抵达,但都必须经过GC root。所以近期的支配点相同也是GC root。

对于点D。无论是从C->D还是C->D->F->D,都必须经过的近期的点是C。所以C是D的支配点。同理可得EFHG在支配树中的位置。

SHALLOWHEAP和RETAINED HEAP

Shallow heap表示对象本身所占内存大小,一个内存大小100bytes的对象Shallow heap就是100bytes。

Retained heap表示通过回收这一个对象总共能回收的内存。比方说一个100bytes的对象还直接或者间接地持有了另外3个100bytes的对象引用,回收这个对象的时候假设另外3个对象没有其他引用也能被回收掉的时候,Retained heap就是400bytes。

在使用mat进行分析时,我们常常接触到的数据就是shallow size和retained size:

Shallow Size

对象自身占用的内存大小。不包含它引用的对象。

针对非数组类型的对象。它的大小就是对象与它全部的成员变量大小的总和。当然这里面还会包含一些java语言特性的数据存储单元。

针对数组类型的对象。它的大小是数组元素对象的大小总和。

Retained Size

Retained Size=当前对象大小+当前对象可直接或间接引用到的对象的大小总和。(间接引用的含义:A->B->C, C就是间接引用)

换句话说。Retained Size就是当前对象被GC后。从Heap上总共能释放掉的内存。

只是,释放的时候还要排除被GC Roots直接或间接引用的对象。他们临时不会被回收。例如以下图:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveXkyNTQxMTc0NDA=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="这里写图片描写叙述" title="">

A对象的Retained Size=A对象的Shallow Size

B对象的Retained Size=B对象的Shallow Size + C对象的Shallow Size

由于B对象被释放时,C同一时候被释放,而D由于被GC roots直接引用所以不会被释放。

而Retained Size就是当前对象被GC后,从Heap上总共能释放掉的内存。

以上概念,都是在使用MAT进行内存分析常常使用的。

我们在分析内存泄漏的时候。着重会查看retained heap。也就是这个对象没有被释放前。retained heap中的相关内存不会被释放。

然后。在分析某个对象为何没被释放的时候,会查看引用关系或者支撑树。

由于引用树父子关系可能比較杂乱,而支撑树更加清晰。

在使用MAT分析内存泄漏的过程中。主要流程就是:

1、分析retained heap。找一个使非常多对象无法被释放的内存。

2、正常情况下,该释放这个对象。所以通过支撑树。或者查看GC 路径,分析为什么这个对象没有被释放。

MAT的下载与使用

下载地址:https://eclipse.org/mat/downloads.php

这里没有作为eclipse插件的方式下载mat,而是通过下载单独的软件client。

首先,在DDMS中选择要检測的进程并dump HPROF file。例如以下图:

HPROF中存储的是当前内存的快照,因此,在dump快照之前先点击cause GC手动触发一次垃圾回收,这样能够避免软引用、弱引用等不必要的对象保留在内存中影响我们的分析。

转储出来的hprof文件,还有使用sdk自带工具进行一下格式转化,工具在sdk路径下的platform-tools下,名称为hprof-conv。

用法:

/.hprof-conv.exe a.hprof b.hprof

a 是输入hprof文件名称,b是输出文件名称。

然后将b.hprof在eclipse memory Analyzer中打开。注意要转换格式。不然无法成功打开。

例如以下:

利用MAT分析内存泄漏

分析过程中,主要使用的是Histogram直方图。和Dominater tree支配树。

在Histogram视图中查找retained heap值最大的项,并分析这里是否发生内存泄漏。

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveXkyNTQxMTc0NDA=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="这里写图片描写叙述" title="">

上图中一坨一坨的。其实就是Class的名称。这样分类比較清晰。后面会说到怎样查看Class声明的对象。

在最上面class Name下有输入过滤的地方,须要注意是。假设要查看com包下的类。那么要输入com. ,这里的正则中’*’貌似不会去匹配’.’,所以就要我们自己输入啦。 普通情况下,我们忽略会java、android系统自带的类,而着重分析我们自己程序中编写的对象内存使用情况。

Retained heap表示由于这个对象,会导致多少对象无法回收。

右击对应类,list objects->with incoming references。

表明引用这个类的某个实例的其他类。也就是它在引用树中的父节点。通过分析该对象被谁引用,来推断为何没被垃圾回收。

outcoming reference就是子节点,查看一些当前对象引用着的对象。

此外看。Merge shortest path to gc root。能够找到一条到GC root的最短路径。来看为什么当前对象无法被回收。


实战分析

以下记录了本人对一个项目的详细分析过程,以及各个工具的用法。

1、使用DDMS查看内存

使用DDMS的过程中。针对应用分别进行了多次检測,主要查看程序执行前的内存使用情况和程序执行后的内存使用情况:

使用前:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveXkyNTQxMTc0NDA=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="这里写图片描写叙述" title="">

使用后:

通过上述数据能够看到,在程序执行前data object也就是在堆上分配的数据是180KB左右,而执行后内存大概在300KB上下浮动,没有呈现一个明显的一直上升的情况,故而没有明显的内存泄漏,基本没有导致OOM的可能。

可是。能够发现,程序执行一次以后,放置一段时间,即便手动触发GC,堆上的内存尽管回落。可是仍然是288KB,与执行前的180KB相差较大,说明有一些对象被GC roots引用,无法完毕释放。

以下採用MAT工具进行进一步分析。在上面的过程中,转出了三个hprof文件,将hprof文件利用Android sdk tools下的工具进行格式转换,进行对照分析:

2、使用MAT分析内存转储

前面分析内存使用发现,使用前和使用后有一个100KB左右的差值,同一时候即便放置一段时间仍然无法使用。

将before和after的直方图增加对照栏,在MAT中进行对照:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveXkyNTQxMTc0NDA=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="这里写图片描写叙述" title="">

点击右上角的红色叹号:

对照发现两个shallow heap大小基本相同,多出的部分是UpdatePartResultThread。系统类而不是我们自己编敲代码造成的。

再看一下使用前后直方图中的retained heap:

能够看出,程序执行后,newActivity强引用了一些对象,在newAcitivity没有推出前。retainedheap部分内存无法被回收。

这也就是我们在DDMS中发现堆内存差异的主要原因。

右击直方图中的NewActivity。能够看见例如以下选项:

用的比較多的是List objects和Merger shortest Paths to GC Roots。

List objects:

Outgoing reference是支配树中当前对象的子节点。也就是当前对象持有哪些引用。

Incoming reference是父节点,即当前对象被谁引用,为什么没被回收。

Merger shortest Paths to GC Roots:找到当前无法被释放的对象到GC roots的最短路径。即排查当前对象被谁引用,为什么没有被释放。这里由于我们的对象是一个Activity,当它显示在前台的时候。不会被垃圾回收,所以不是我们分析的点。

在这里。我们查看outgoing reference,查看当前对象拥有哪些强引用:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveXkyNTQxMTc0NDA=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="这里写图片描写叙述" title="">

排除系统的对象。还是主要分析我们编写的程序。

最后发现。我们在之前使用LeakCanary时,注冊的对应监听器没有回收。发现了内存泄漏 :)。

去掉LeakCanary,再次測试发现data object的值确实下降了不少。

继续分析,发现newActivity引用了一个

致使一部分内存无法被释放。

这个问题属于client实现问题。不在内存泄漏的范围内。

接下来,在直方图中过滤出服务端的类:



能够看到,服务端的类大部分shallow heap都为0,也就是已经被垃圾回收。

结论

在使用MAT分析内存时,最关键的就是找引用关系。假设一个应该被释放的对象没有被释放,那么我们往往要查看它的incoming reference,看看是谁持有了它的强引用。同一时候利用Merger shortest GC roots找到到GC root的最短路径。确定是由于被谁引用而导致无法GC。

Android内存泄漏检測与MAT使用的更多相关文章

  1. LeakCanary:简单粗暴的内存泄漏检測工具

    差点儿每一个程序猿在开发的过程中都会遇到内存泄漏.那么我们怎样检測到app是否哪里出现内存泄漏呢?square公司推出了一款简单粗暴的检測内存泄漏的工具-- LeakCanary 什么是内存泄漏? 内 ...

  2. C++内存泄露检測原理

    转自:http://hi.baidu.com/jasonlyy/item/9ca0cecf2c8f113a99b4981c 本文针对 linux 下的 C++ 程序的内存泄漏的检測方法及事实上现进行探 ...

  3. Android 内存泄漏优化汇总

    android内存泄漏优化摘要 博客分类: android android内存溢出OutOfMemoryError . android移动应用程序的内存分配一般是8凯瑟琳约,不正确地假定处理内存处理非 ...

  4. Android内存泄漏检测利器:LeakCanary

    Android内存泄漏检测利器:LeakCanary MAR 28TH, 2016 是什么? 一言以蔽之:LeakCanary是一个傻瓜化并且可视化的内存泄露分析工具 为什么需要LeakCanary? ...

  5. Android 内存泄漏分析与解决方法

    在分析Android内存泄漏之前,先了解一下JAVA的一些知识 1. JAVA中的对象的创建 使用new指令生成对象时,堆内存将会为此开辟一份空间存放该对象 垃圾回收器回收非存活的对象,并释放对应的内 ...

  6. Android内存泄漏的检测流程、捕捉以及分析

    https://blog.csdn.net/qq_20280683/article/details/77964208 Android内存泄漏的检测流程.捕捉以及分析 简述: 一个APP的性能,重度关乎 ...

  7. android 内存泄漏,以及检测方法

    1.为什么会产生内存泄漏 当一个对象已经不需要再使用本该被回收时,另外一个正在使用的对象持有它的引用从而导致它不能被回收,这导致本该被回收的对象不能被回收而停留在堆内存中,这就产生了内存泄漏. 2.内 ...

  8. Android内存泄漏原因

    这段时间调试APP的时候,发现程序在加载了过多的bitmap后会崩溃.查看了日志,原来是发生了内存溢出(OOM).第一次遇到这样的问题,那就慢慢排查吧. 内存优化可以参考胡凯大神的博客Android内 ...

  9. Visual Studio内存泄露检測工具

    使用简单介绍     在敲代码的过程中.难免会遇到内存泄露的时候.这个时候假设手工查找内存泄露,不说方法没有通用的,就是真的要自己手工查找也是非常耗时间和精力的.诚然.我们能够借助一些工具,并且我们还 ...

随机推荐

  1. iwebshop 模板手册

    1.面包屑 <div class="position"><span>您当前的位置:</span><a href="{url:}& ...

  2. ASP.NET Core 2.0使用Log4net实现记录日志功能

    一.安装Log4net 1.使用Nuget包进行安装 在依赖项上面右键,选择“管理NuGet程序包”,如下图所示: 在浏览界面输入log4net,然后点击安装,如下图所示: 2.使用程序包管理器控制台 ...

  3. org.thymeleaf.exceptions.TemplateInputException: Error resolving template "/ template might not exist or might not be accessible by any of the configured

    异常现象:在本地打包部署完全没有问题,资源文件也都可以映射上,但是打包成jar包部署到服务器上时,就一直报异常,异常信息如下: 严重: Servlet.service() for servlet [d ...

  4. PHP和MySQL实现消息队列

    最近遇到一个批量发送短信的需求,短信接口是第三方提供的.刚开始想到,获取到手机号之后,循环调用接口发送不就可以了吗? 但很快发现问题:当短信数量很大时,不仅耗时,而且成功率很低. 于是想到,用PHP和 ...

  5. MWeb 的基本使用

    MWeb 的文档库模式和外部模式 MWeb 分为文档库模式和外部模式,下面分别说明. 文档库模式 软件一开始打开就是文档库模式了,开始时需要设置文档库保存的位置,之后所有文档都会保存在这个位置.文档库 ...

  6. C# .NET - Sql Bulk Insert from multiple delimited Textfile using c#.net

    SqlBulkCopy.WriteToServer has 4 overloads:SqlBulkCopy.WriteToServer (DataRow[])    Copies all rows f ...

  7. OSPF的特征、术语、包类型、邻居关系的建立、RID的选择、DR和BDR的选举、度量值的计算、默认路由、验证

    链路状态路由协议OSPF的特征.术语.包类型.邻居关系的建立.RID的选择.DR和BDR的选举.度量值的计算.默认路由.验证等. 文章目录 [*1*].链路状态路由协议概述 工作过程 优缺点 [*2* ...

  8. ssh : how to add "hostkey" to “know_hosts”

    有时后端daemon或者脚本在执行ssh连接时,会遇到以下提示: The authenticity of host 'git.sws.com (10.42.1.88)' can't be establ ...

  9. ERROR:tornado上传文件过大超出范围报错

    该怎么解决呢? HTTPServer里面指定max_buffer_size就可以了 EXAMPLE # server = HTTPServer(application, max_buffer_size ...

  10. GitHub developer API 学习

    官网地址:https://developer.github.com/v3/ 目录 当前版本 schema parameters root endpoint client errors http red ...