性能优化-内存分析工具的使用(整理)

前文讲到了内存泄漏的原因,那么要怎么定位内存泄漏呢?这里列出了常用的分析工具及其使用方法
以下Heap SnapshotMATHeap ViewerAllaction TrackingLeakCanaryTraceView资料均来源于网络

Heap Snapshot

获取Java堆内存详细信息,可以分析出内存泄漏的问题
在2.X版本中,Android Studio使用的分析工具
点击Monitor便可查看CPUMemoryNetworkGPU的情况
Heap Dump启动
其打开面板如下:
Heap Snapshot控制面板
该面板里的信息可以有三种类型:app heap/image heap/zygote heap
分别代表app堆内存信息,图片堆内存信息,zygote进程的堆内存信息
Heap Snapshot控制面板详细信息

A区域

列举了堆内存中所有的类,一下是列表中列名:

名称 意义
Total Count 内存中该类的对象个数
Heap Count 堆内存中该类的对象个数
Sizeof 物理大小
Shallow size 该对象本身占有内存大小
Retained Size 释放该对象后,节省的内存大小

B区域

当我们点击某个类时,右边的B区域会显示该类的实例化对象,这里面会显示有多少个实体,以及详细信息
Heap Snapshot B区域

名称 意义
depth 深度
Shallow Size 对象本身内存大小
Dominating Size 管辖的内存大小

当你点击某个对象时,将展开该对象内部含有哪些对象,同时C区域也会显示哪些对象引用了该对象

C区域

Heap Snapshot C区域
点击查看
Heap Snapshot C展开区域
某对象引用树对象,在这里面能看出其没谁引用了,比如在内存泄漏中,可以看出来它被谁引用,比如上图,引用树的第一行,可以看出来,该对象被Object[12]对象引用,索引值为1,那我们展开后,可以看到,该Object[12]是一个ArrayList

在3.X版本,Android Studio采用了新的分析工具,但其使用都是类似的
其启动界面如下
Android Profiler
分析界面如下
Android Profiler具体分析界面

MAT

下载:http://eclipse.org/mat/downloads.php
MAT工具全称为Memory Analyzer Tool,一款详细分析Java堆内存的工具,该工具非常强大,为了使用该工具,我们需要hprof文件。但是该文件不能直接被MAT使用,需要进行一步转化,可以使用hprof-conv命令来转化,但是Android Studio可以直接转化,转化方法如下
选择一个hprof文件,点击右键选择Export to standard .hprof选项
MAT Android转化1
MAT工具所需的文件就生成了,下面我们用MAT来打开该工具:

  1. 打开MAT后选择File -> Open File选择我们刚才生成的hprof文件
  2. 选择该文件后,MAT会有几秒种的时间解析该文件,有的hprof文件可能过大,会有更长的时间解析,解析后,展现在我们的面前的界面如下
    MAT 界面
    这是个总览界面,会大体给出一些分析后初步的结论
  • Overview视图
    该视图会首页总结出当前这个Heap dump占用了多大的内存,其中涉及的类有多少,对象有多少,类加载器,如果有没有回收的对象,会有一个连接,可以直接参看(图中的Unreachable Objects Histogram)。
    比如该例子中显示了Heap dump占用了41M的内存,5400个类,96700个对象,6个类加载器。
    然后还会有各种分类信息:
    • Biggest Objects by Retained Size
      会列举出Retained Size值最大的几个值,你可以将鼠标放到饼图中的扇叶上,可以在右侧看出详细信息,在这里可以找到我们关心的内容
    • histogram视图
      histogram视图主要是查看某个类的实例个数,比如我们在检查内存泄漏时候,要判断是否频繁创建了对象,就可以来看对象的个数来看。也可以通过排序看出占用内存大的对象,默认是类名形式展示,也可以选择不同的显示方式
    • Dominator tree视图
      该视图会以占用总内存的百分比来列举所有实例对象,注意这个地方是对象而不是类了,这个视图是用来发现大内存对象的。这些对象都可以展开查看更详细的信息,可以看到该对象内部包含的对象
    • Leaks suspects视图
      这个视图会展示一些可能的内存泄漏的点

Navigation History中可以选择Histogram,然后右键加入对比,实现多个histogram数据的对比结果,从而分析内存泄漏的可能性

Heap Viewer

实时查看App分配的内存大小和空闲内存大小
发现Memory Leaks

  • 使用条件
    5.0以上的系统,包括5.0
    开发者选项可用

在2.x的Android Studio中,
可以直接在Android studio工具栏中直接点击小机器人启动
还可以在Android studio的菜单栏中Tools
或者是在sdk的tools工具下打开
在3.x的IDE中,默认已经找不到启动图标,但在tools目录下依旧可以打开使用

Heap Viewer面板如下
Heap Viewer面板
按上图的标记顺序按下,我们就能看到内存的具体数据,右边面板中数值会在每次GC时发生改变,包括App自动触发或者你来手动触发
总览:
Heap Viewer面板总览

列名 意义
Heap Size 堆栈分配给App的内存大小
Allocated 已分配使用的内存大小
Free 空闲的内存大小
%Used Allocated/Heap Size,使用率
Objects 对象数量

详情:
Heap Viewer详情

类型 意义
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 平均值

当我们点击某一行时,可以看到如下的柱状图
Heap Viewer柱状图
横坐标是对象的内存大小,这些值随着不同对象是不同的,纵坐标是在某个内存大小上的对象的数量

使用:在需要检测内存泄漏的用例执行过后,手动GC下,然后观察data object一栏的total size(也可以观察Heap Size/Allocated内存的情况),看看内存是不是会回到一个稳定值,多次操作后,只要内存是稳定在某个值,那么说明没有内存溢出的,如果发现内存在每次GC后,都在增长,不管是慢增长还是快速增长,都说明有内存泄漏的可能性

Allaction Tracking

追踪内存分配信息。可以很直观地看到某个操作的内存是如何进行一步一步地分配的
Allocation Tracker(AS)工具比Allocation Tracker(Eclipse)工具强大的地方是更炫酷,更清晰,但是能做的事情都是一样的

Allocation Tracker启动
Allocation Tracker启动
在内存图中点击途中标红的部分,启动追踪,再次点击就是停止追踪,随后自动生成一个alloc结尾的文件,这个文件就记录了这次追踪到的所有数据,然后会在右上角打开一个数据面板
Allocation Tracker数据面板
面板左上角是所有历史数据文件列表,后面是详细信息,好,现在我们来看详细介绍信息面板
Allocation Tracker详细介绍信息面板
下面我们用字母来分段介绍

  • A:查看方式选项
    A标识的是一个选择框,有2个选项
    Allocation TrackerA标识
    Group by Method:用方法来分类我们的内存分配
    Group by Allocator:用内存分配器来分类我们的内存分配
    不同的选项,在D区显示的信息会不同,默认会以Group by Method来组织,我们来看看详细信息:
    Allocation TrackerA标识信息
    从上图可以看出,首先以线程对象分类,默认以分配顺序来排序,当然你可以更改,只需在Size上点击一下就会倒序,如果以Count排序也是一样,Size就是内存大小,Count就是分配了多少次内存,点击一下线程就会查看每个线程里所有分配内存的方法,并且可以一步一步迭代到最底部
    Allocation TrackerA标识信息底部
    当你以Group by Allocator来查看内存分配的情况时,详细信息区域就会变成如下
    Allocation TrackerA标识信息底部详细信息
    默认还是以内存分配顺序来排序,但是是以每个分配者第一次分配内存的顺序
    Allocation TrackerA标识信息内存分配顺序
    这种方式显示的好处,是我们很好的定位我们自己的代码的分析信息,比如上图中,以包名来找到我们的程序,在这次追踪中包民根目录一共有五个类作为分配器分配了78-4-1=73次内存
  • B:Jump To Source按钮
    如果我们想看内存分配的实际在源码中发生的地方,可以选择需要跳转的对象,点击该按钮就能发现我们的源码,但是前提是你有源码
    Allocation TrackerB标识
    如果你能跳转到源码,Jump To Source按钮才是可用的,都是跳转到类
  • C:统计图标按钮
    该按钮比较酷炫,如果点击该按钮,会弹出一个新窗口,里面是一个酷炫的统计图标,有柱状图和轮胎图两种图形可供选择,默认是轮胎图,其中分配比例可以选择分配次数和占用内存大小,默认是大小Size
  • 轮胎图
    Allocation TrackerC标识轮胎图
    轮胎图是以圆心为起点,最外层是其内存实际分配的对象,每一个同心圆可能被分割成多个部分,代表了其不同的子孙,每一个同心圆代表他的一个后代,每个分割的部分代表了某一带人有多人,你双击某个同心圆中某个分割的部分,会变成以你点击的那一代为圆心再向外展开。如果想回到原始状态,双击圆心就可以了。
    1.起点
    Allocation TrackerC标识轮胎图起点
    圆心是我们的起点处,如果你把鼠标放到我图中标注的区域,会在右边显示当前指示的是什么线程(Thread1)以及具体信息(分配了8821次,分配了564.18k的内存),但是红框标注的区域并不代表Thread1,而是第一个同心圆中占比最大的那个线程,所以我们现在把鼠标放到第一个同心圆上,可以看出来,我们划过同心圆的轨迹时可以看到右边的树枝变化了好几个值
    Allocation TrackerC标识轮胎图起点信息
    2.查看某一个扇面
    我们刚打开是全局信息,我们如果想看其中某个线程,详细信息,可以顺着某个扇面向外围滑动,当然如果你觉得不还是不清晰,可以双击该扇面全面展现该扇面的信息
    Allocation TrackerC标识轮胎图查看信息
    在某个地方双击时,新的轮胎图是以双击点为圆心,你如果想到刚才的圆,双击圆心空白处就可以
    Allocation TrackerC标识轮胎图双击查看
    3.一个内存的完整路径
    Allocation TrackerC标识轮胎图内存完整路径
  • 柱状图
    Allocation TrackerC标识柱状图
    柱状图以左边为起始点,从左到右的顺序是某个的堆栈信息顺序,纵坐标上的宽度是以其Count/Size的大小决定的。柱状图的内容其实和轮胎图没什么特别的地方
    1.起点
    Allocation TrackerC标识柱状图
    2.查看某一个分支
    Allocation TrackerC标识柱状图查看分支
    3.Count/Size切换
    Allocation TrackerC标识柱状图切换

LeakCanary

可以直接在手机端查看内存泄露的工具
实现原理:本质上还是用命令控制生成hprof文件分析检查内存泄露

添加LeakCanary依赖包

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'

LeakCanary添加依赖

开启LeakCanary

添加Application子类
首先创建一个ExampleApplication,该类继承于Application,在该类的onCreate方法中添加如下代码开启LeakCanary监控:
LeakCanary.install(this);
LeakCanary添加Application子类

在配置文件中注册ExampleApplication

AndroidManifest.xml中的application标签中添加如下信息:
android:name=".ExampleApplication"
LeakCanary注册ExampleApplication

这个时候安装应用到手机,会自动安装一个Leaks应用,如下图
LeakCanary安装信息

制造一个内存泄漏的点

建立一个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();
    }
}

我们从首页跳转到详情页的时候会进入DetailActivityonCreate的方法,然后就将当前activity添加到了数组中,当返回时,我们没把他从数组中删除。再次进入的时候,会创建新的activity并添加到数组中,但是之前的activity仍然被引用,无法释放,但是这个activity不会再被使用,这个时候就造成了内存泄漏。我们来看看LeakCanary是如何报出这个问题的

演示

LeakCanary演示
解析的过程有点耗时,所以需要等待一会才会在Leaks应用中,当我们点开某一个信息时,会看到详细的泄漏信息
LeakCanary演示1

TraceView

从代码层面分析性能问题,针对每个方法来分析,比如当我们发现我们的应用出现卡顿的时候,我们可以来分析出现卡顿时在方法的调用上有没有很耗时的操作,关注以下两个问题:

  • 调用次数不多,但是每一次执行都很耗时
  • 方法耗时不大,但是调用次数太多
    简单一点来说就是我们能找到频繁被调用的方法,也能找到执行非常耗时的方法,前者可能会造成cpu频繁调用,手机发烫的问题,后者就是卡顿的问题

TraceView工具启动

打开Monitor,点击图中的标注的按钮,启动追踪
TraceView工具启动

TraceView工具面板

打开App操作你的应用后,再次点击的话就停止追踪并且自动打开traceview分析面板
TraceView工具面板
traceview的面板分上下两个部分:

  • 时间线面板以每个线程为一行,右边是该线程在整个过程中方法执行的情况
  • 分析面板是以表格的形式展示所有线程的方法的各项指标

时间线面板

TraceView时间线面板
左边是线程信息,main线程就是Android应用的主线程,这个线程是都会有的,其他的线程可能因操作不同而发生改变.每个线程的右边对应的是该线程中每个方法的执行信息,左边为第一个方法执行开始,最右边为最后一个方法执行结束,其中的每一个小立柱就代表一次方法的调用,你可以把鼠标放到立柱上,就会显示该方法调用的详细信息
TraceView显示方法调用
你可以随意滑动你的鼠标,滑倒哪里,左上角就会显示该方法调用的信息。
1.如果你想在分析面板中详细查看该方法,可以双击该立柱,分析面板自动跳转到该方法
TraceView显示详细信息
2.放大某个区域
刚打开的面板中,是我们采集信息的总览,但是一些局部的细节我们看不太清,没关系,该工具支持我们放大某个特殊的时间段
TraceView放大某个区域
如果想回到最初的状态,双击时间线就可以
3.每一个方法的表示
TraceView方法的表示
可以看出来,每一个方法都是用一个凹型结构来表示,坐标的凸起部分表示方法的开始,右边的凸起部分表示方法的结束,中间的直线表示方法的持续

分析面板

面板列名含义如下

名称 意义
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 实际时间于调用次数的百分比,该表该函数平均执行时间

你可以点击某个函数展开更详细的信息
TraceView函数展开
展开后,大多数有以下两个类别:

  • 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.201375.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=531375.201/531 = 0.7065Cpu Time/Call也是吻合的,580.668/531=1.0935,和Real Time/Call一栏也是吻合的
TraceView计算

  • Parents
    现在我们来看该方法的Parents一栏
    TraceViewParents
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
    TraceViewChildren
    可以看出来,我们的子类有2个,一个是自身,一个是23android/view/View.draw(L android/graphics/Canvas;)Vself代表自身方法中的语句执行情况,由上面可以看出来,该方法没有多余语句,直接调用了其子类方法。另外一个子类方法,可以看出被当前方法调用了177次,但是该方法被其他方法调用过,因为他的总调用次数为892次,你可以点击进入子类方法的详细信息中

  • Parents while recursive
    TraceViewParents while recursive
    列举了递归调用当前方法的父类方法,以及其递归次数的占比,犹豫我们当前的方法递归了354次,以上三个父类方法递归的次数分别为348+4+2=354

  • Children while recursive
    TraceViewChildren while recursive
    列举了当递归调用时调用的子类方法

Lint分析工具

检测资源文件是否有没有用到的资源。
检测常见内存泄露
安全问题
SDK版本安全问题
是否有费的代码没有用到
代码的规范—甚至驼峰命名法也会检测
自动生成的罗列出来
没用的导包
可能的bug

Analyze -> Inspect Code便可执行检查
可以检查project,Module和指定文件
Link分析工具
详细信息
Link分析工具分析界面

posted @ 2019-04-06 20:44  cj5785  阅读(822)  评论(0编辑  收藏  举报