前文讲到了内存泄漏的缘由,那么要怎么定位内存泄漏呢?这里列出了经常使用的分析工具及其使用方法
如下Heap Snapshot
、MAT
、Heap Viewer
、Allaction Tracking
、LeakCanary
和TraceView
资料均来源于网络php
获取Java堆内存详细信息,能够分析出内存泄漏的问题
在2.X版本中,Android Studio
使用的分析工具
点击Monitor
即可查看CPU
,Memory
,Network
,GPU
的状况
其打开面板以下:
该面板里的信息能够有三种类型:app heap/image heap/zygote heap
分别表明app堆内存信息,图片堆内存信息,zygote
进程的堆内存信息
java
列举了堆内存中全部的类,一下是列表中列名:android
名称 | 意义 |
---|---|
Total Count |
内存中该类的对象个数 |
Heap Count |
堆内存中该类的对象个数 |
Sizeof |
物理大小 |
Shallow size |
该对象自己占有内存大小 |
Retained Size |
释放该对象后,节省的内存大小 |
当咱们点击某个类时,右边的B区域会显示该类的实例化对象,这里面会显示有多少个实体,以及详细信息
git
名称 | 意义 |
---|---|
depth |
深度 |
Shallow Size |
对象自己内存大小 |
Dominating Size |
管辖的内存大小 |
当你点击某个对象时,将展开该对象内部含有哪些对象,同时C区域也会显示哪些对象引用了该对象github
点击查看
某对象引用树对象,在这里面能看出其没谁引用了,好比在内存泄漏中,能够看出来它被谁引用,好比上图,引用树的第一行,能够看出来,该对象被Object[12]
对象引用,索引值为1,那咱们展开后,能够看到,该Object[12]
是一个ArrayList
web
在3.X版本,Android Studio
采用了新的分析工具,但其使用都是相似的
其启动界面以下
分析界面以下
数组
下载:http://eclipse.org/mat/downloads.php
MAT工具全称为Memory Analyzer Tool
,一款详细分析Java堆内存的工具,该工具很是强大,为了使用该工具,咱们须要hprof
文件。可是该文件不能直接被MAT使用,须要进行一步转化,可使用hprof-conv
命令来转化,可是Android Studio
能够直接转化,转化方法以下
选择一个hprof
文件,点击右键选择Export to standard .hprof
选项
MAT工具所需的文件就生成了,下面咱们用MAT来打开该工具:安全
File -> Open File
选择咱们刚才生成的hprof
文件hprof
文件可能过大,会有更长的时间解析,解析后,展示在咱们的面前的界面以下Overview
视图Heap dump
占用了多大的内存,其中涉及的类有多少,对象有多少,类加载器,若是有没有回收的对象,会有一个链接,能够直接参看(图中的Unreachable Objects Histogram
)。Heap dump
占用了41M的内存,5400个类,96700个对象,6个类加载器。Biggest Objects by Retained Size
histogram视图
Dominator tree视图
Leaks suspects视图
在Navigation History
中能够选择Histogram
,而后右键加入对比,实现多个histogram
数据的对比结果,从而分析内存泄漏的可能性网络
实时查看App分配的内存大小和空闲内存大小
发现Memory Leaks
app
在2.x的Android Studio
中,
能够直接在Android studio
工具栏中直接点击小机器人启动
还能够在Android studio
的菜单栏中Tools
或者是在sdk的tools
工具下打开
在3.x的IDE中,默认已经找不到启动图标,但在tools
目录下依旧能够打开使用
Heap Viewer
面板以下
按上图的标记顺序按下,咱们就能看到内存的具体数据,右边面板中数值会在每次GC时发生改变,包括App自动触发或者你来手动触发
总览:
列名 | 意义 |
---|---|
Heap Size |
堆栈分配给App的内存大小 |
Allocated |
已分配使用的内存大小 |
Free |
空闲的内存大小 |
%Used |
Allocated/Heap Size ,使用率 |
Objects |
对象数量 |
详情:
类型 | 意义 |
---|---|
free |
空闲的对象 |
data object |
数据对象,类类型对象,最主要的观察对象 |
class object |
类类型的引用对象 |
1-byte array(byte[],boolean[]) |
一个字节的数组对象 |
2-byte array(short[],char[]) |
两个字节的数组对象 |
4-byte array(long[],double[]) |
4个字节的数组对象 |
non-Java object |
非Java对象 |
下面是每个对象都有的列名含义
列名 | 意义 |
---|---|
Count |
数量 |
Total Size |
总共占用的内存大小 |
Smallest |
将对象占用内存的大小从小往大排,排在第一个的对象占用内存大小 |
Largest |
将对象占用内存的大小从小往大排,排在最后一个的对象占用的内存大小 |
Median |
将对象占用内存的大小从小往大排,拍在中间的对象占用的内存大小 |
Average |
平均值 |
当咱们点击某一行时,能够看到以下的柱状图
横坐标是对象的内存大小,这些值随着不一样对象是不一样的,纵坐标是在某个内存大小上的对象的数量
使用:在须要检测内存泄漏的用例执行事后,手动GC下,而后观察data object
一栏的total size
(也能够观察Heap Size/Allocated
内存的状况),看看内存是否是会回到一个稳定值,屡次操做后,只要内存是稳定在某个值,那么说明没有内存溢出的,若是发现内存在每次GC后,都在增加,不论是慢增加仍是快速增加,都说明有内存泄漏的可能性
追踪内存分配信息。能够很直观地看到某个操做的内存是如何进行一步一步地分配的
Allocation Tracker(AS)
工具比Allocation Tracker(Eclipse)
工具强大的地方是更炫酷,更清晰,可是能作的事情都是同样的
Allocation Tracker
启动
在内存图中点击途中标红的部分,启动追踪,再次点击就是中止追踪,随后自动生成一个alloc结尾的文件,这个文件就记录了此次追踪到的全部数据,而后会在右上角打开一个数据面板
面板左上角是全部历史数据文件列表,后面是详细信息,好,如今咱们来看详细介绍信息面板
下面咱们用字母来分段介绍
Group by Method
:用方法来分类咱们的内存分配Group by Allocator
:用内存分配器来分类咱们的内存分配Group by Method
来组织,咱们来看看详细信息:Size
上点击一下就会倒序,若是以Count
排序也是同样,Size
就是内存大小,Count
就是分配了多少次内存,点击一下线程就会查看每一个线程里全部分配内存的方法,而且能够一步一步迭代到最底部Group by Allocator
来查看内存分配的状况时,详细信息区域就会变成以下78-4-1=73
次内存Jump To Source
按钮Jump To Source
按钮才是可用的,都是跳转到类Size
Thread1
)以及具体信息(分配了8821
次,分配了564.18k
的内存),可是红框标注的区域并不表明Thread1
,而是第一个同心圆中占比最大的那个线程,因此咱们如今把鼠标放到第一个同心圆上,能够看出来,咱们划过同心圆的轨迹时能够看到右边的树枝变化了好几个值Count/Size
的大小决定的。柱状图的内容其实和轮胎图没什么特别的地方能够直接在手机端查看内存泄露的工具
实现原理:本质上仍是用命令控制生成hprof
文件分析检查内存泄露
https://github.com/square/leakcanary
在主模块app下的build.gradle
下添加以下依赖
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3.1' releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3.1'
添加Application
子类
首先建立一个ExampleApplication
,该类继承于Application
,在该类的onCreate方法中添加以下代码开启LeakCanary
监控:
LeakCanary.install(this);
在AndroidManifest.xml
中的application
标签中添加以下信息:
android:name=".ExampleApplication"
这个时候安装应用到手机,会自动安装一个Leaks应用,以下图
创建一个ActivityManager类,单例模式,里面有一个数组用来保存Activity:
package com.example.android.sunshine.app; import android.app.Activity; import android.util.SparseArray; import android.view.animation.AccelerateInterpolator; import java.util.List; public class ActivityManager { private SparseArray<Activity> container = new SparseArray<Activity>(); private int key = 0; private static ActivityManager mInstance; private ActivityManager(){} public static ActivityManager getInstance(){ if(mInstance == null){ mInstance = new ActivityManager(); } return mInstance; } public void addActivity(Activity activity){ container.put(key++,activity); } }
而后在DetailActivity中的onCreate方法中将当前activity添加到ActivityManager的数组中:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_detail); ActivityManager.getInstance().addActivity(this); if (savedInstanceState == null) { // Create the detail fragment and add it to the activity // using a fragment transaction. Bundle arguments = new Bundle(); arguments.putParcelable(DetailFragment.DETAIL_URI, getIntent().getData()); DetailFragment fragment = new DetailFragment(); fragment.setArguments(arguments); getSupportFragmentManager().beginTransaction() .add(R.id.weather_detail_container, fragment) .commit(); } }
咱们从首页跳转到详情页的时候会进入DetailActivity
的onCreate
的方法,而后就将当前activity
添加到了数组中,当返回时,咱们没把他从数组中删除。再次进入的时候,会建立新的activity
并添加到数组中,可是以前的activity
仍然被引用,没法释放,可是这个activity
不会再被使用,这个时候就形成了内存泄漏。咱们来看看LeakCanary
是如何报出这个问题的
解析的过程有点耗时,因此须要等待一会才会在Leaks应用中,当咱们点开某一个信息时,会看到详细的泄漏信息
从代码层面分析性能问题,针对每一个方法来分析,好比当咱们发现咱们的应用出现卡顿的时候,咱们能够来分析出现卡顿时在方法的调用上有没有很耗时的操做,关注如下两个问题:
打开Monitor,点击图中的标注的按钮,启动追踪
打开App操做你的应用后,再次点击的话就中止追踪而且自动打开traceview分析面板
traceview
的面板分上下两个部分:
左边是线程信息,main线程就是Android应用的主线程,这个线程是都会有的,其余的线程可能因操做不一样而发生改变.每一个线程的右边对应的是该线程中每一个方法的执行信息,左边为第一个方法执行开始,最右边为最后一个方法执行结束,其中的每个小立柱就表明一次方法的调用,你能够把鼠标放到立柱上,就会显示该方法调用的详细信息
你能够随意滑动你的鼠标,滑倒哪里,左上角就会显示该方法调用的信息。
1.若是你想在分析面板中详细查看该方法,能够双击该立柱,分析面板自动跳转到该方法
2.放大某个区域
刚打开的面板中,是咱们采集信息的总览,可是一些局部的细节咱们看不太清,不要紧,该工具支持咱们放大某个特殊的时间段
若是想回到最初的状态,双击时间线就能够
3.每个方法的表示
能够看出来,每个方法都是用一个凹型结构来表示,坐标的凸起部分表示方法的开始,右边的凸起部分表示方法的结束,中间的直线表示方法的持续
面板列名含义以下
名称 | 意义 |
---|---|
Name |
方法的详细信息,包括包名和参数信息 |
Incl Cpu Time |
Cpu执行该方法该方法及其子方法所花费的时间 |
Incl Cpu Time % |
Cpu执行该方法该方法及其子方法所花费占Cpu总执行时间的百分比 |
Excl Cpu Time |
Cpu执行该方法所话费的时间 |
Excl Cpu Time % |
Cpu执行该方法所话费的时间占Cpu总时间的百分比 |
Incl Real Time |
该方法及其子方法执行所话费的实际时间,从执行该方法到结束一共花了多少时间 |
Incl Real Time % |
上述时间占总的运行时间的百分比 |
Excl Real Time % |
该方法自身的实际容许时间 |
Excl Real Time |
上述时间占总的容许时间的百分比 |
Calls+Recur |
调用次数+递归次数,只在方法中显示,在子展开后的父类和子类方法这一栏被下面的数据代替 |
Calls/Total |
调用次数和总次数的占比 |
Cpu Time/Call |
Cpu执行时间和调用次数的百分比,表明该函数消耗cpu的平均时间 |
Real Time/Call |
实际时间于调用次数的百分比,该表该函数平均执行时间 |
你能够点击某个函数展开更详细的信息
展开后,大多数有如下两个类别:
Parents
:调用该方法的父类方法Children
:该方法调用的子类方法若是该方法含有递归调用,可能还会多出两个类别:
Parents while recursive
:递归调用时所涉及的父类方法Children while recursive
:递归调用时所涉及的子类方法首先咱们来看当前方法的信息
列 | 值 |
---|---|
Name |
24 android/widget/FrameLayout.draw(L android/graphics/Canvas;)V |
Incl Cpu% |
20.9% |
Incl Cpu Time |
375.201 |
Excl Cpu Time % |
0.0% |
Excl Cpu Time |
0.000 |
Incl Real Time % |
1.1% |
Incl Real Time |
580.668 |
Excl Real Time % |
0.0% |
Excl Real Time |
0.000 |
Calls+Recur |
177+354 |
Cpu Time/Call |
0.707 |
Real Time/Call |
1.094 |
根据下图中的toplevel能够看出总的cpu执行时间为1797.167ms
,当前方法占用cpu的时间为375.201
,375.201/1797.167=0.2087
,和咱们的Incl Cpu Time%
是吻合的。当前方法消耗的时间为580.668
,而toplevel
的时间为53844.141ms
,580.668/53844.141=1.07%
,和Incl Real Time %
也是吻合的。在来看调用次数为177
,递归次数为354
,和为177+354=531
,375.201/531 = 0.7065
和Cpu Time/Call
也是吻合的,580.668/531=1.0935
,和Real Time/Call
一栏也是吻合的
Parents
Parents
一栏列 | 值 |
---|---|
Name |
22 com/android/internal/policy/impl/PhoneWindow$DecorView.draw(Landroid/graphics/Canvas;)V |
Incl Cpu% |
100% |
Incl Cpu Time |
375.201 |
Excl Cpu Time % |
无 |
Excl Cpu Time |
无 |
Incl Real Time % |
100% |
Incl Real Time |
580.668 |
Excl Real Time % |
无 |
Excl Real Time |
无 |
Call/Total |
177/531 |
Cpu Time/Call |
无 |
Real Time/Call |
无 |
其中的Incl Cpu Time%
变成了100%
,由于在这个地方,总时间为当前方法的执行时间,这个时候的Incl Cpu Time%
只是计算该方法调用的总时间中被各父类方法调用的时间占比,好比Parents
有2个父类方法,那就能看出每一个父类方法调用该方法的时间分布。由于咱们父类只有一个,因此确定是100%
,Incl Real Time
一栏也是同样的,重点是Call/Total
,以前咱们看当前方式时,这一栏的列名为Call+Recur
,而如今变成了Call/Total
,这个里面的数值变成了177/531
,由于总次数为531
次,父类调用了177
次,其余531
次是递归调用。这一数据能获得的信息是,当前方法被调用了多少次,其中有多少次是父类方法调用的
Children
能够看出来,咱们的子类有2个,一个是自身,一个是23android/view/View.draw(L android/graphics/Canvas;)V
,self
表明自身方法中的语句执行状况,由上面能够看出来,该方法没有多余语句,直接调用了其子类方法。另一个子类方法,能够看出被当前方法调用了177次,可是该方法被其余方法调用过,由于他的总调用次数为892次,你能够点击进入子类方法的详细信息中
Parents while recursive
列举了递归调用当前方法的父类方法,以及其递归次数的占比,犹豫咱们当前的方法递归了354
次,以上三个父类方法递归的次数分别为348+4+2=354
次
Children while recursive
列举了当递归调用时调用的子类方法
检测资源文件是否有没有用到的资源。
检测常见内存泄露
安全问题
SDK版本安全问题
是否有费的代码没有用到
代码的规范—甚至驼峰命名法也会检测
自动生成的罗列出来
没用的导包
可能的bug
Analyze -> Inspect Code
即可执行检查
能够检查project,Module和指定文件
详细信息