失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > Android内存泄漏检测工具使用手册

Android内存泄漏检测工具使用手册

时间:2020-02-07 12:12:23

相关推荐

Android内存泄漏检测工具使用手册

Android内存泄漏检测工具使用手册

前言LeakCanary在Android中接入LeakCanaryLeakCanary内存泄漏分析内存泄漏上报到服务端SharkShark分析当前应用的内存泄漏情况Shark分析hprof文件Android Profile捕获堆转储进行分析MAT安装MAT使用MAT处理导入hprof文件寻找内存泄漏的类分析被泄露的类的引用关系找到最终的泄漏的地方Merge对比分析Jhat-Java自带的性能监测工具Show heap histogramExecute Object Query Language (OQL) querydumpsys meminfo读取垃圾回收消息(GC Log)Dalvik 日志消息ART 日志消息

前言

性能优化除过我们平时自己设计和开发之外就得考虑使用工具进行检测。Android关于能够定位和剖析问题的内存工具有很多,但不是每个工具所有场景都能覆盖到。

DDMSLeakCanaryhaha/sharkAndroid ProfileMATJhatdumpsys meminfoAPTLeakInspectorChrome DevtoolGC Log

现在对平时能发现问题,而且使用简单的一些工具的使用进行整理,并且对这个LeakCanaryTestActivity页面进行内存泄漏的分析。

public class LeakCanaryTestActivity extends BaseActivity {private static Test test;@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);test = new Test(this);}private static class Test{public Test(Context context) {this.context = context;}private Context context;private int a;private int b;}}

LeakCanary

LeakCanary 官网

LeakCanary的原理很简单: 在ActivityFragment被销毁后, 将他们的引用包装成一个WeakReference, 然后将这个WeakReference关联到一个ReferenceQueue。查看ReferenceQueue中是否含有ActivityFragment的引用。如果没有触发GC后再次查看。还是没有的话就说明回收成功, 否则可能发生了泄露. 这时候开始dump内存的信息,并分析泄露的引用链。

在Android中接入LeakCanary

dependencies {debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.3'}

LeakCanary2.0之前我们接入的时候需要在Application.onCreate方法中显式调用LeakCanary.install(this);开启LeakCanary的内存监控。

LeakCanary2.0开始通过自己注册的provider自己开启LeakCanary的内存监控。我们平时开发用的Instant Run运行过程中也使用的是这种静默方式进行启动。

<provider android:name="com.android.tools.ir.server.InstantRunContentProvider" android:multiprocess="true" android:authorities="com..android.tools.ir.server.InstantRunContentProvider"/>

LeakCanary内存泄漏分析

在进行debug或者UI自动化测试的时候,我们会在通知栏看到有关内存泄漏的提示。查看详情后我们能看到相关的内存泄漏具体位置,存在泄露的成员变量都用波浪线进行的标识。

内存泄漏上报到服务端

LeakCanary升级到2.0betafinal版本之后 shark 官网 文档提供的的内存泄漏上报方式对应的API已经过时,我们需要实现新的接口将LeakCanary捕获的内存泄漏进行上报。

class LeakUploader : OnHeapAnalyzedListener {override fun onHeapAnalyzed(heapAnalysis: HeapAnalysis) {TODO("Upload heap analysis to server")//HeapAnalysis的toString和2.0之前的版本的LeakCanary.leakInfo获得的信息类似println(heapAnalysis)}}class MyApplication : Application() {override fun onCreate() {super.onCreate()LeakCanary.config = LeakCanary.config.copy(onHeapAnalyzedListener = LeakUploader())}}

Shark

shark 官网

Shark是为LeakCanary 2提供支持的堆分析器,它是Kotlin独立堆分析库,可在低内存占用情况下高速运行(PS:LeakCanary 2之前的堆分析库是haha,haha Git地址)。

此处说的LeakCanary 2betafinal版本,alpha版依旧是用的haha只不过是用kotlin写的。

Shark在为LeakCanary 2提供支持的同事也提供Shark CLI支持。

Shark命令行界面(CLI)使您可以直接从计算机分析堆。它可以转储安装在已连接的Android设备上的应用程序的堆,对其进行分析,甚至剥离所有敏感数据(例如PII,密码或加密密钥)的堆转储,这在共享堆转储时非常有用。

Shark分析当前应用的内存泄漏情况

shark-cli --device 设备id --process 包名 analyze

同时支持混淆后的内存泄漏分析,利用mapping文件进行可读性还原。

shark-cli -d 设备id -p 包名 -m 混淆文件 analyze

Shark分析hprof文件

shark-cli -h 生成的hprof文件 analyze

Android Profile

Android Profiler分为三大模块:cpu内存网络

官网:使用 Memory Profiler 查看 Java 堆和内存分配

Memory ProfilerAndroid Profiler中的一个组件,它可以帮助您识别内存泄漏和内存溢出,从而导致存根、冻结甚至应用程序崩溃。它显示了应用程序内存使用的实时图,让您捕获堆转储、强制垃圾收集和跟踪内存分配。

捕获堆转储进行分析

在列表的顶部,您可以使用右下拉菜单在列表之间切换:

Arrange by class: 根据类名分配。Arrange by package:根据包名分配。Arrange by callstack: 根据调用堆栈排序。

查看堆转储后的信息:

您的应用程序分配了哪些类型的对象,以及每个对象的数量;每个对象使用多少内存;每个对象的引用被保留在你的代码中;调用堆栈,用于分配对象的位置(只有在记录分配时捕获堆转储);

MAT安装

打开Eclipse->help->Eclipse Marketplce,搜索Memory Analyze进行安装,安装完成后重启Eclipse

MAT使用

dump heap生成的hprof文件转化为MAT能处理的hprof文件。

执行android.os.Debug.dumpHprofData(hprofPath)生成hprof文件,执行之前记得进行GC

hprof-conv位于sdk/platform-tools/hprof-conv

hprof-conv memory-android.hprof memory-mat.hprof

MAT处理导入hprof文件

Action有一下几个视图:

寻找内存泄漏的类

根据内存中类的对象实例数量,判断该类对象是否被泄露。

我们可以利用提供的多种检索方式进行目标类的检索,我这里用包名作为检索要素。

Shallow Size

对象自身占用的内存大小,不包括它引用的对象。针对非数组类型的对象,它的大小就是对象与它所有的成员变量大小的总和。当然这里面还会包括一些java语言特性的数据存储单元。针对数组类型的对象,它的大小是数组元素对象的大小总和。

Retained Size

Retained Size=当前对象大小+当前对象可直接或间接引用到的对象的大小总和。(间接引用的含义:A->B->C, C就是间接引用。如果BC没有被其他对象引用,那么RetainedSize-A = ShallowSize(A + B + C)它和 Dominator 比较相似)

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

不过,释放的时候还要排除被GC Roots直接或间接引用的对象。他们暂时不会被被当做Garbage

从上图可以看出MainActivityLeakCanaryTestActivityLeakCanaryTestActivity$a都有一个实例没有被回收。

分析被泄露的类的引用关系

选择没有回收的类,进行list objects -> with incoming references操作得到被引用的对象。

with outgoing references: 该对象内部引用了那些其他对象;

with incoming references: 该对象被谁进行了引用;

得到被引用的类之后,进行Path To GC Roots -> exclude all phantom/weak/soft etc. references操作,得到所有引用类型的引用。

StrongReference(强引用):通常我们编写的代码都是StrongReference,于此对应的是强可达性,只有去掉强可达,对象才被回收。

SoftReference(软引用):只要有足够的内存,就一直保持对象,直到发现内存吃紧且没有StrongReference时才回收对象。一般可用来实现缓存,需要获取对象时,可以调用get方法。

WeakReference(弱引用):随时可能会被垃圾回收器回收,不一定要等到虚拟机内存不足时才强制回收。要获取对象时,同样可以调用get方法。

PhantomReference(虚引用):根本不会在内存中保持任何对象,你只能使用PhantomReference本身。一般用于在进入finalize()方法后进行特殊的清理过程。

找到最终的泄漏的地方

从这个图中我们可以可以得到:

LeakCanaryTestActivity的一个实例被它的内部类LeakCanaryTestActivity$Test的成员变量context所持有;LeakCanaryTestActivity$Test的一个实例又被LeakCanaryTestActivity的成员变量test所持有。

Merge对比分析

如果我们没有明确的目标类,我们可以将两个hprof文件(泄漏前、泄漏后)进行对比。

选择泄漏之前的hprof文件进行对比。

对比会得到哪些实例对象数量的增加和减少。如上图所示对比结果为LeakCanaryTestActivityLeakCanaryTestActivity$a(此处的a为混淆之后的Test)两个类梳理分别增加1个。

我们继续向上面MAT分析步骤一样操作:

进行list objects -> with incoming references操作;

进行Path To GC Roots -> exclude all phantom/weak/soft etc. references操作;

最终得到的结果和之前分析的相同的。

Jhat-Java自带的性能监测工具

Java8 jhat Analyzes the Java heap docs

JHatOracle推出的一款Hprof分析软件,它和MAT并称为Java 内存静态分析利器。不同于MAT的单人界面式分析,jHat使用多人界面式分析。它被内置在 JDK 中,在命令行中输入jhat命令可查看有没有相应的命令。

➜ Desktop jhatERROR: No arguments suppliedUsage: jhat [-stack <bool>] [-refs <bool>] [-port <port>] [-baseline <file>] [-debug <int>] [-version] [-h|-help] <file>-J<flag>Pass <flag> directly to the runtime system. Forexample, -J-mx512m to use a maximum heap size of 512MB-stack false:Turn off tracking object allocation call stack.-refs false:Turn off tracking of references to objects-port <port>:Set the port for the HTTP server. Defaults to 7000-exclude <file>: Specify a file that lists data members that shouldbe excluded from the reachableFrom query.-baseline <file>: Specify a baseline object dump. Objects inboth heap dumps with the same ID and same class willbe marked as not being "new".-debug <int>:Set debug level.0: No debug output1: Debug hprof file parsing2: Debug hprof file parsing, no server-versionReport version number-h|-helpPrint this help and exit<file> The file to readFor a dump file that contains multiple heap dumps,you may specify which dump in the fileby appending "#<number>" to the file name, i.e. "foo.hprof#3".All boolean options default to "true"

Jhat使用的hprof文件和MAT一样都需要使用hprof-conv进行hprof转化。

使用Jhat分析完hprof文件后会给一个Server port,比如7000。那么我们可以访问http://localhost:7000/查看分析结果。

以包为单位展示所有的类,我们下拉到最底部可以看到有其他的查询方式。

Show heap histogram

我们可以看到对应的类的内存实例数量以及占用对应的内存大小。

http://localhost:7000/histo/

Execute Object Query Language (OQL) query

可以使用OQL查询~!

OQL查询语法与Visual VMOQL类似~ 基本语法如下:

select <JavaScript expression to select>[ from [instanceof] <class name> <identifier>[ where <JavaScript boolean expression to filter> ] ]

我们点击某个类之后可以看到该类的详细信息:

Exclude subclasses相当于MATwith outgoing references: 该对象内部引用了那些其他对象;

Include subclasses相当于MAT 的with incoming references: 该对象被谁进行了引用;

先查看类的实例,然后再查看每个实例的相关引用情况。

dumpsys meminfo

Android系统是基于Linux内核的操作系统,所以在Linux中查看内存使用情况的命令在Android手机上也能使用比如top命令。除此之外

procrank:获取所有进程的内存使用情况,排序是按照Pss大小,详细输出每个PID对应的VssRssPssUssSwapPSwapUSwapZSwapcmdline。但该命令使用需要root环境。

一般来说内存占用大小有如下规律:VSS>=RSS>=PSS>=USS

USS是针对某个进程开始有可疑内存泄露的情况,进行检测的最佳数字。怀疑某个程序有内存泄露可以查看这个值是否一直有增加。

cat /proc/meminfo:展示系统整体的内存情况,按照内存类型进行分类。free:查看可用内存,缺省单位为KB。该命令比较简单、轻量,专注于查看剩余内存情况。数据来源于/proc/meminfo

最后一个是本次叙述的重点dumpsys

dumpsys [options]meminfo 显示内存信息cpuinfo 显示CPU信息account 显示accounts信息activity 显示所有的activities的信息window 显示键盘,窗口和它们的关系wifi 显示wifi信息

使用dumpysys meminfo查看内存信息,后面可以添加pid | packagename查看该应用程序的内存信息。

~/Desktop adb shell dumpsys meminfo com.tzx.androidcodeApplications Memory Usage (in Kilobytes):Uptime: 131873995 Realtime: 240892295** MEMINFO in pid 19924 [com.tzx.androidcode] **Pss Private Private SwapPssHeapHeapHeapTotal Dirty Clean DirtySize AllocFree------ ------ ------ ------ ------ ------ ------Native Heap 11062 11032 0 99 38912 21656 17255Dalvik Heap40793984 0 0563828192819Dalvik Other14051404 0 1Stack 64 64 0 0Ashmem 2 0 0 0Gfx dev20522052 0 0Other dev 8 0 8 0.so mmap959 80 64 4.jar mmap1062 0 24 0.apk mmap109 0 0 0.ttf mmap 33 0 0 0.dex mmap4513 364392 0.oat mmap197 0 0 0.art mmap62295924 16 1Other mmap680196 96 0EGL mtrack 19320 19320 0 0GL mtrack63926392 0 0Unknown11061092 0 15TOTAL 59392 515764600120 44550 24475 4App SummaryPss(KB)------Java Heap:9924Native Heap: 11032Code:4596Stack: 64Graphics: 27764Private Other:2796System:3216TOTAL: 59392 TOTAL SWAP PSS:120ObjectsViews: 82 ViewRootImpl: 2AppContexts: 8 Activities: 2Assets: 11 AssetManagers: 0Local Binders: 22 Proxy Binders: 41Parcel memory: 10 Parcel count: 24Death Recipients: 2OpenSSL Sockets: 0WebViews: 0SQLMEMORY_USED: 0PAGECACHE_OVERFLOW: 0MALLOC_SIZE: 0

Android程序内存被分为2部分:native虚拟机虚拟机就是我们平常说的java堆,我们创建的对象是在这里面分配的,而bitmap是直接在native上分配的,对于内存的限制是native+dalvik不能超过最大限制。以上信息可以看到该应用程序占用的nativedalvik,对于分析内存泄露,内存溢出都有极大的作用。

读取垃圾回收消息(GC Log)

官网:读取垃圾回收消息

Dalvik 日志消息

Dalvik(而不是ART)中,每个GC都会将以下信息输出到logcat中:

D/dalvikvm(PID): GC_Reason Amount_freed, Heap_stats, External_memory_stats, Pause_time

示例:

D/dalvikvm( 9050): GC_CONCURRENT freed 2049K, 65% free 3571K/9991K, external 4703K/5261K, paused 2ms+2ms

ART 日志消息

Dalvik不同,ART不会为未明确请求的GC记录消息。只有在系统认为GC速度较慢时才会输出GC消息。更确切地说,仅在GC暂停时间超过 5 毫秒或GC持续时间超过 100 毫秒时。如果应用未处于可察觉到暂停的状态(例如应用在后台运行时,这种情况下,用户无法察觉GC暂停),则其所有GC都不会被视为速度较慢。系统一直会记录显式GC

ART会在其垃圾回收日志消息中包含以下信息:

I/art: GC_Reason GC_Name Objects_freed(Size_freed) AllocSpace Objects,Large_objects_freed(Large_object_size_freed) Heap_stats LOS objects, Pause_time(s)

示例:

I/art : Explicit concurrent mark sweep GC freed 104710(7MB) AllocSpace objects,21(416KB) LOS objects, 33% free, 25MB/38MB, paused 1.230ms total 67.216ms

文章到这里就全部讲述完啦,若有其他需要交流的可以留言哦!!

想阅读作者的更多文章,可以查看我 个人博客 和公共号:

如果觉得《Android内存泄漏检测工具使用手册》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。