内存分析_内存分析工具 - CSDN
精华内容
参与话题
  • 内存分析工具讲解

    千次阅读 2014-02-05 15:27:10
    在使用Memory Analyzer tool(MAT)分析内存泄漏(一)中,我介绍了内存泄漏的前因后果。在本文中,将介绍MAT如何根据heap dump分析泄漏根源。由于测试范例可能过于简单,很容易找出问题,但我期待借此举一反三。 一...


    前言

    使用Memory Analyzer tool(MAT)分析内存泄漏(一)中,我介绍了内存泄漏的前因后果。在本文中,将介绍MAT如何根据heap dump分析泄漏根源。由于测试范例可能过于简单,很容易找出问题,但我期待借此举一反三。
    一开始不得不说说ClassLoader,本质上,它的工作就是把磁盘上的类文件读入内存,然后调用java.lang.ClassLoader.defineClass方法告诉系统把内存镜像处理成合法的字节码。Java提供了抽象类ClassLoader,所有用户自定义类装载器都实例化自ClassLoader的子类。system class loader在没有指定装载器的情况下默认装载用户类,在Sun Java 1.5中既sun.misc.Launcher$AppClassLoader。更详细的内容请参看下面的资料。


    准备heap dump

    请看下面的Pilot类,没啥特殊的。

    /**
     * Pilot class
     * 
    @author rosen jiang
     
    */
    package org.rosenjiang.bo;

    public class Pilot{
        
        String name;
        int age;
        
        public Pilot(String a, int b){
            name = a;
            age = b;
        }
    }

    然后再看OOMHeapTest类,它是如何撑破heap dump的。

    /**
     * OOMHeapTest class
     * 
    @author rosen jiang
     
    */
    package org.rosenjiang.test;

    import java.util.Date;
    import java.util.HashMap;
    import java.util.Map;
    import org.rosenjiang.bo.Pilot;

    public class OOMHeapTest {
        public static void main(String[] args){
            oom();
        }
        
        private static void oom(){
            Map<String, Pilot> map = new HashMap<String, Pilot>();
            Object[] array = new Object[1000000];
            for(int i=0; i<1000000; i++){
                String d = new Date().toString();
                Pilot p = new Pilot(d, i);
                map.put(i+"rosen jiang", p);
                array[i]=p;
            }
        }
    }

    是的,上面构造了很多的Pilot类实例,向数组和map中放。由于是Strong Ref,GC自然不会回收这些对象,一直放在heap中直到溢出。当然在运行前,先要在Eclipse中配置VM参数-XX:+HeapDumpOnOutOfMemoryError。好了,一会儿功夫内存溢出,控制台打出如下信息。

    java.lang.OutOfMemoryError: Java heap space
    Dumping heap to java_pid3600.hprof 
    Heap dump file created [78233961 bytes in 1.995 secs]
    Exception in thread "main" java.lang.OutOfMemoryError: Java heap space



    java_pid3600.hprof既是heap dump,可以在OOMHeapTest类所在的工程根目录下找到。

    MAT安装

    话分两头说,有了heap dump还得安装MAT。可以在http://www.eclipse.org/mat/downloads.php选择合适的方式安装。安装完成后切换到Memory Analyzer视图。在Eclipse的左上角有Open Heap Dump按钮,按照刚才说的路径找到java_pid3600.hprof文件并打开。解析hprof文件会花些时间,然后会弹出向导,直接Finish即可。稍后会看到下图所示的界面。



    MAT工具分析了heap dump后在界面上非常直观的展示了一个饼图,该图深色区域被怀疑有内存泄漏,可以发现整个heap才64M内存,深色区域就占了99.5%。接下来是一个简短的描述,告诉我们main线程占用了大量内存,并且明确指出system class loader加载的"java.lang.Thread"实例有内存聚集,并建议用关键字"java.lang.Thread"进行检查。所以,MAT通过简单的两句话就说明了问题所在,就算使用者没什么处理内存问题的经验。在下面还有一个"Details"链接,在点开之前不妨考虑一个问题:为何对象实例会聚集在内存中,为何存活(而未被GC)?是的——Strong Ref,那么再走近一些吧。



    点击了"Details"链接之后,除了在上一页看到的描述外,还有Shortest Paths To the Accumulation Point和Accumulated Objects部分,这里说明了从GC root到聚集点的最短路径,以及完整的reference chain。观察Accumulated Objects部分,java.util.HashMap和java.lang.Object[1000000]实例的retained heap(size)最大,在上一篇文章中我们知道retained heap代表从该类实例沿着reference chain往下所能收集到的其他类实例的shallow heap(size)总和,所以明显类实例都聚集在HashMap和Object数组中了。这里我们发现一个有趣的现象,既Object数组的shallow heap和retained heap竟然一样,通过Shallow and retained sizes一文可知,数组的shallow heap和一般对象(非数组)不同,依赖于数组的长度和里面的元素的类型,对数组求shallow heap,也就是求数组集合内所有对象的shallow heap之和。好,再来看org.rosenjiang.bo.Pilot对象实例的shallow heap为何是16,因为对象头是8字节,成员变量int是4字节、String引用是4字节,故总共16字节。



    接着往下看,来到了Accumulated Objects by Class区域,顾名思义,这里能找到被聚集的对象实例的类名。org.rosenjiang.bo.Pilot类上头条了,被实例化了290,325次,再返回去看程序,我承认是故意这么干的。还有很多有用的报告可用来协助分析问题,只是本文中的例子太简单,也用不上。以后如有用到,一定撰文详细叙述。

    又是perm gen

    我们在上一篇文章中知道,perm gen是个异类,里面存储了类和方法数据(与class loader有关)以及interned strings(字符串驻留)。在heap dump中没有包含太多的perm gen信息。那么我们就用这些少量的信息来解决问题吧。

    看下面的代码,利用interned strings把perm gen撑破了。

    /**
     * OOMPermTest class
     * 
    @author rosen jiang
     
    */
    package org.rosenjiang.test;

    public class OOMPermTest {
        public static void main(String[] args){
            oom();
        }
        
        private static void oom(){
            Object[] array = new Object[10000000];
            for(int i=0; i<10000000; i++){
                String d = String.valueOf(i).intern();
                array[i]=d;
            }
        }
    }

    控制台打印如下的信息,然后把java_pid1824.hprof文件导入到MAT。其实在MAT里,看到的状况应该和“OutOfMemoryError: Java heap space”差不多(用了数组),因为heap dump并没有包含interned strings方面的任何信息。只是在这里需要强调,使用intern()方法的时候应该多加注意。

    java.lang.OutOfMemoryError: PermGen space
    Dumping heap to java_pid1824.hprof 
    Heap dump file created [121273334 bytes in 2.845 secs]
    Exception in thread "main" java.lang.OutOfMemoryError: PermGen space



    倒是在思考如何把class loader撑破废了些心思。经过尝试,发现使用ASM来动态生成类才能达到目的。ASM(http://asm.objectweb.org)的主要作用是处理已编译类(compiled class),能对已编译类进行生成、转换、分析(功能之一是实现动态代理),而且它运行起来足够的快和小巧,文档也全面,实属居家必备之良品。ASM提供了core API和tree API,前者是基于事件的方式,后者是基于对象的方式,类似于XML的SAX、DOM解析,但是使用tree API性能会有损失。既然下面要用到ASM,这里不得不啰嗦下已编译类的结构,包括:
        1、修饰符(例如public、private)、类名、父类名、接口和annotation部分。
        2、类成员变量声明,包括每个成员的修饰符、名字、类型和annotation。
        3、方法和构造函数描述,包括修饰符、名字、返回和传入参数类型,以及annotation。当然还包括这些方法或构造函数的具体Java字节码。
        4、常量池(constant pool)部分,constant pool是一个包含类中出现的数字、字符串、类型常量的数组。



    已编译类和原来的类源码区别在于,已编译类只包含类本身,内部类不会在已编译类中出现,而是生成另外一个已编译类文件;其二,已编译类中没有注释;其三,已编译类没有package和import部分。
    这里还得说说已编译类对Java类型的描述,对于原始类型由单个大写字母表示,Z代表boolean、C代表char、B代表byte、S代表short、I代表int、F代表float、J代表long、D代表double;而对类类型的描述使用内部名(internal name)外加前缀L和后面的分号共同表示来表示,所谓内部名就是带全包路径的表示法,例如String的内部名是java/lang/String;对于数组类型,使用单方括号加上数据元素类型的方式描述。最后对于方法的描述,用圆括号来表示,如果返回是void用V表示,具体参考下图。



    下面的代码中会使用ASM core API,注意接口ClassVisitor是核心,FieldVisitor、MethodVisitor都是辅助接口。ClassVisitor应该按照这样的方式来调用:visit visitSource? visitOuterClass? ( visitAnnotation | visitAttribute )*( visitInnerClass | visitField | visitMethod )* visitEnd。就是说visit方法必须首先调用,再调用最多一次的visitSource,再调用最多一次的visitOuterClass方法,接下来再多次调用visitAnnotation和visitAttribute方法,最后是多次调用visitInnerClass、visitField和visitMethod方法。调用完后再调用visitEnd方法作为结尾。

    注意ClassWriter类,该类实现了ClassVisitor接口,通过toByteArray方法可以把已编译类直接构建成二进制形式。由于我们要动态生成子类,所以这里只对ClassWriter感兴趣。首先是抽象类原型:

    /**
     * 
    @author rosen jiang
     * MyAbsClass class
     
    */
    package org.rosenjiang.test;

    public abstract class MyAbsClass {
        int LESS = -1;
        int EQUAL = 0;
        int GREATER = 1;
        abstract int absTo(Object o);
    }

    其次是自定义类加载器,实在没法,ClassLoader的defineClass方法都是protected的,要加载字节数组形式(因为toByteArray了)的类只有继承一下自己再实现。

    /**
     * 
    @author rosen jiang
     * MyClassLoader class
     
    */
    package org.rosenjiang.test;

    public class MyClassLoader extends ClassLoader {
        public Class defineClass(String name, byte[] b) {
            return defineClass(name, b, 0, b.length);
        }
    }

    最后是测试类。

    /**
     * 
    @author rosen jiang
     * OOMPermTest class
     
    */
    package org.rosenjiang.test;

    import java.util.ArrayList;
    import java.util.List;
    import org.objectweb.asm.ClassWriter;
    import org.objectweb.asm.Opcodes;

    public class OOMPermTest {
        public static void main(String[] args) {
            OOMPermTest o = new OOMPermTest();
            o.oom();
        }

        private void oom() {
            try {
                ClassWriter cw = new ClassWriter(0);
                cw.visit(Opcodes.V1_5, Opcodes.ACC_PUBLIC + Opcodes.ACC_ABSTRACT,
                "org/rosenjiang/test/MyAbsClass", null, "java/lang/Object",
                new String[] {});
                cw.visitField(Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL + Opcodes.ACC_STATIC, "LESS", "I",
                nullnew Integer(-1)).visitEnd();
                cw.visitField(Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL + Opcodes.ACC_STATIC, "EQUAL", "I",
                nullnew Integer(0)).visitEnd();
                cw.visitField(Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL + Opcodes.ACC_STATIC, "GREATER", "I",
                nullnew Integer(1)).visitEnd();
                cw.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_ABSTRACT, "absTo",
                "(Ljava/lang/Object;)I", nullnull).visitEnd();
                cw.visitEnd();
                byte[] b = cw.toByteArray();

                List<ClassLoader> classLoaders = new ArrayList<ClassLoader>();
                while (true) {
                    MyClassLoader classLoader = new MyClassLoader();
                    classLoader.defineClass("org.rosenjiang.test.MyAbsClass", b);
                    classLoaders.add(classLoader);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    不一会儿,控制台就报错了。

    java.lang.OutOfMemoryError: PermGen space
    Dumping heap to java_pid3023.hprof 
    Heap dump file created [92593641 bytes in 2.405 secs]
    Exception in thread "main" java.lang.OutOfMemoryError: PermGen space


    打开java_pid3023.hprof文件,注意看下图的Classes: 88.1k和Class Loader: 87.7k部分,从这点可看出class loader加载了大量的类。



    更进一步分析,点击上图中红框线圈起来的按钮,选择Java Basics——Class Loader Explorer功能。打开后能看到下图所示的界面,第一列是class loader名字;第二列是class loader已定义类(defined classes)的个数,这里要说一下已定义类和已加载类(loaded classes)了,当需要加载类的时候,相应的class loader会首先把请求委派给父class loader,只有当父class loader加载失败后,该class loader才会自己定义并加载类,这就是Java自己的“双亲委派加载链”结构;第三列是class loader所加载的类的实例数目。



    在Class Loader Explorer这里,能发现class loader是否加载了过多的类。另外,还有Duplicate Classes功能,也能协助分析重复加载的类,在此就不再截图了,可以肯定的是MyAbsClass被重复加载了N多次。

    最后

    其实MAT工具非常的强大,上面故弄玄虚的范例代码根本用不上MAT的其他分析功能,所以就不再描述了。其实对于OOM不只我列举的两种溢出错误,还有多种其他错误,但我想说的是,对于perm gen,如果实在找不出问题所在,建议使用JVM的-verbose参数,该参数会在后台打印出日志,可以用来查看哪个class loader加载了什么类,例:“[Loaded org.rosenjiang.test.MyAbsClass from org.rosenjiang.test.MyClassLoader]”。
    全文完。


    参考资料

    memoryanalyzer Blog
    java类加载器体系结构
    ClassLoader


    请注意!引用、转贴本文应注明原作者:Rosen Jiang 以及出处: http://www.blogjava.net/rosen


    展开全文
  • 前文讲到了内存泄漏的原因,那么要怎么定位内存泄漏呢?这里列出了常用的分析工具及其使用方法 以下Heap Snapshot、MAT、Heap Viewer、Allaction Tracking、LeakCanary和TraceView资料均来源于网络 Heap Snapshot ...

    前文讲到了内存泄漏的原因,那么要怎么定位内存泄漏呢?这里列出了常用的分析工具及其使用方法
    以下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分析工具分析界面

    展开全文
  • 内存分析

    2019-07-23 10:01:40
    1.获取程序运行的id号 (jps) 2.获取对应进程的dump.log文件 jmap -dump:live,format=b,file=dump.log 23354 (这个命令最后面跟进程id即获取...3.jdk自带分析工具目录<JDK_HOME>/bin/jvisualvm.exe 双...

    1.获取程序运行的id号  (jps)

    2.获取对应进程的dump.log文件

     

    jmap -dump:live,format=b,file=dump.log 23354                             (这个命令最后面跟进程id即获取对应进程的dump文件)

    3.jdk自带分析工具目录<JDK_HOME>/bin/jvisualvm.exe  双击打开

    4.分析结果

    展开全文
  • 内存分析(详解与代码)

    千次阅读 2020-05-09 14:57:55
    要想了解Java的低层是如何运作的,更扎实的明白Java的数据存储,内存分析是必不可少的。 Java虚拟机的内存可以分为三个区域:栈,堆和方法区(实际上是2个,方法区实际上是一种特殊的堆,存在堆里面),不管是堆,...

     要想了解Java的低层是如何运作的,更扎实的明白Java的数据存储,内存分析是必不可少的。

    Java虚拟机的内存可以分为三个区域:栈,堆和方法区(实际上是2个,方法区实际上是一种特殊的堆,存在堆里面),不管是堆,栈还是方法区,都有相应的特点,存放相应的东西。

    堆的特点:

    1、堆用于存储创建好的对象和数组(数组也是对象)

    2、JVM只有一个堆,被所有线程共享

    3、堆是一个不连续的内存空间,分配灵活,速度慢!

    栈的特点:

    1、栈描述的是方法执行的内存模型,每个方法被调用都会创建一个栈帧(存储局部变量、操作数、方法出口等)

    2、JVM为每个线程创建一个栈,用于存放该线程执行方法的信息(实际参数、局部变量等)

    3、栈属于线程私有,不能实现线程间的共享!

    4、栈的存储特点是:先进后出,后进先出

    5、栈是由系统自动分配,速度快!栈是一个连续的内存空间!

    方法区(又叫静态区)特点:

    1、JVM只有一个方法区,被所有线程共享!

    2、方法区实际也是堆,只是用于存储类、常量相关的信息!

    3、用来存放程序中永远是不变或唯一的内容。(类信息【Class对象】,静态变量、字符串常量等)

    下面我们就举个例子,说明一下内存分析,先贴下代码,我们有简单的3个类,公司类,部门类,员工类。 

     公司类:

    public class Company {
    	private String name = "我房旅居集团";			//8
    
    	public String getName() {
    		return name;
    	}
    
    	public void setName(String name) {
    		this.name = name;
    	}
    	
    }

    部门类: 

    public class Department {
    
    	private String name;
    	private Company company;
    	
    	public String getName() {
    		return name;
    	}
    
    	public void setName(String name) {
    		this.name = name;
    	}
    
    	public Company getCompany() {
    		return company;
    	}
    
    	public void setCompany(Company company) {
    		this.company = company;
    	}
    	
    	
    }

     员工类:

    public class Staff {
    
    	private static boolean ISANIMAL = true;
    	private String name;
    	private int age;
    	Department department;
    	
    	public String staffInformation(){
    		String string = "";			//16、23
    		//17、24
    		string = "用户为:"+department.getCompany().getName()+department.getName()+name;
    		return string;
    	}
    	public static boolean isAnimal(){
    		return ISANIMAL;			//19
    	}
    	//javac Staff.java		java Staff     				//1
    	public static void main(String[] args) {			//2
    		Staff staff = new Staff();						//3、4
    		staff.name = "林高禄";							//5
    		staff.age = 28;									//6
    		Company company = new Company();				//7、8、9
    		Department department = new Department();		//10、11
    		department.setCompany(company);					//12
    		department.setName("系统开发部");				//13
    		staff.department = department;					//14
    		String iInformation = staff.staffInformation();	//15、18
    		System.out.println(iInformation);				//19
    		System.out.println(Staff.isAnimal());			//20
    		company.setName("橙云科技");						//21
    		iInformation = staff.staffInformation();		//22、25
    		System.out.println(iInformation);				//26
    	}
    
    	
    }

    注:以上代码中的注释1-26,代表的是代码再内存分析中的执行步骤。

    下面我们就进行内存分析:

    第1步 :将java文件编译成class文件,类信息【Class对象】,静态变量、字符串常量等都存放到方法区中。

    第2步:main方法的栈帧执行,staff为空

    第3步:调用Staff类的构造器,先创建对象,属性赋值,后调用构造器赋值

    第4步:Staff类的构造器调完,栈帧出栈,堆内内存地址赋予staff

     第5步:staff通过内存地址找到name,把"林高禄"的地址赋予给staff.name

    第6步:staff通过内存地址找到age,把age赋值为28

      第7步:调用Company构造器

     第8步:把"我房旅居集团"的地址赋予给Company类的name

     第9步:Company类的构造器调完,栈帧出栈,堆内内存地址赋予company

    第10步:调用Department构造器

     第11步:Department类的构造器调完,栈帧出栈,堆内内存地址赋予department

     第12步:把company的内存地址赋予给department的company

     第13步:department通过内存地址找到name,把"系统开发部"的地址赋予给department.name

      第14步:把department的内存地址赋予给staff的department

    第15步:调用staffInformation方法入栈

    第16步:把“”的地址赋予给staffInformation方法内部的string

    第17步:通过一些列get方法加上字符串拼接,组装成新字符串,并且把新字符串的地址赋值给staffInformation方法内部的string

     第18步:staffInformation方法调完,栈帧出栈,把staffInformation方法返回的字符串地址赋予给iInformation,内部的string也被回收。

    第19步:打印iInformation

    第20步: 调用isAnimal方法入栈,返回值,调用完栈帧出栈,打印返回的值

    第21步: company通过内存地址找到name,把"橙云科技"的地址赋予给company.name(这里内存地址是不一样的,画图输入错误)

     第22步:调用staffInformation方法入栈

     第23步:把“”的地址赋予给staffInformation方法内部的string

     第24步:通过一些列get方法加上字符串拼接,组装成新字符串,并且把新字符串的地址赋值给staffInformation方法内部的string

     第25步:staffInformation方法调完,栈帧出栈,把staffInformation方法返回的字符串地址赋予给iInformation,内部的string也被回收。

     第26步:打印iInformation

    到这里,这个内存分析简单的介绍完了,如果有什么不对的地方,还请大家指出来。 

    展开全文
  • 性能优化——内存分析工具的使用

    千次阅读 2018-05-30 08:10:47
    本文将介绍比较常用的的内存泄漏检测工具,包括HeapSnapShot、HeapViewer、MAT一、HeapSnapShot的使用HeapSnapShot意思是堆快照,通过堆内存的信息来分析内存泄漏的问题。1、启动HeapSnapShot2、显示Heap SnapShot...
  • 内存分析工具

    千次阅读 2018-05-26 00:38:49
    内存分析工具1. 测试工具介绍1.1测试目的java内存泄漏是每个Java程序员都会遇到的问题,程序在本地运行一切正常,可是布署到远端就会出现内存无限制的增长,最后系统瘫痪,那么如何最快最好的检测程序的稳定性,防止系统...
  • Linux常见的内存分析工具

    千次阅读 2019-02-17 15:08:00
    随着技术的日新月异,嵌入式软件产品也逐渐往更高端, ...本文将列出几个我比较熟悉的内存分析的工具或者方法,读者不妨可以尝试使用以下。 1. system monitor图像化界面 在ubuntu左上角点击search your comput...
  • 浅谈内存分析

    2012-03-10 00:18:10
    只知其表,不知其理。这是我们衡量一个人专业知识是否有深度的基本法则?作为初来乍到的程序猿来说,内存分析是我们编写速度快、效率高的代码必不可少的知识。...(一)先解释一下静态内存分析与动态内存分析
  • jmap、 jhat 分析内存溢出

    千次阅读 2017-03-04 18:34:01
    查看该进程下堆内存的使用情况 jmap -heap 1963(进程号) 查看存活对象的内存使用情况 jmap -histo:live 1963 ...3.还可以使用jhat分析内存溢出的原因 使用dump内存信息到heap.bin文件 jmap -dump:live
  • 网上资料中有很多说明,但是没有解析的太清楚,我这里分析汇总了一下。供大家参数。学习知识,要关注细节,懂与不懂差很多的呦!我说的是薪资:)。 Native Heap:Native代码分配的内存,虚拟机和Android框架分配...
  • linux下分析Java程序内存汇总

    万次阅读 2015-06-12 16:31:25
    使用pmap查看进程内存 运行命令 使用pmap可以查看某一个进程(非java的也可以)的内存使用使用情况, 命令格式: pmap 进程id 示例说明 例如运行: pmap 12358 显示结果如下图(内容较多, 分成几张图说明)...
  • 一次内存溢出的填坑经历

    千次阅读 2015-08-28 16:51:26
    在项目运行过程中,可能会出现内存溢出,内存溢出的原因多种多样,而在内存溢出后,我们如何查找和分析内存溢出的原因呢?这里来说一说我遇到的次遇到的内存溢出经历。 大致情况是这样的:应用在启动后,过一段时间...
  • poi解决内存消耗过大溢出问题

    千次阅读 2016-03-18 08:03:50
    poi解析引擎的解析速度是非常快的,一般2000条数据200多毫秒就解析完成了,但是这带来的是巨大的内存消耗,当文件过大,或者多人同时使用这个引擎 这个时候就容易导致内存不足而溢出,使得java虚拟机抛出OutOfMemory...
  • 对APP进行dumpsys meminfo内存分析解读

    万次阅读 2018-09-29 13:40:07
    使用adb shell dumpsys meminfo分析app内存截图: Native Heap:Native代码分配的内存,虚拟机和Android框架分配内存。关于什么是Native代码,即非Java代码分配的内存。详细介绍请找百度。 Dalvik Heap:Java...
  • APP运行占用内存分析

    千次阅读 2015-09-13 21:34:46
    对于手机来说,运行内存还是挺珍贵的,因此,如果你的APP能够占用尽量少的内存,无疑会更加受用户欢迎。下面说说APP运行时占用内存分析: 测试方法: 一、使用Android Studio自带的工具Memory monitor,来测试。...
  • idea 可 以 分 析 存 快 照 分 析 吗 ? 如 果 可 以 怎 么 玩?
  • 学习 Java 虚拟机能深入地理解 Java 这门语言,想要深入学习java的各种细节,很多时候你要深入到字节码层次去分析,你才能得到准确的结论,通过学习JVM你了解JVM历史,内存结构、垃圾回收机制、性能监控工具、认识类...
  • Android中如何查找内存泄露

    万次阅读 2013-03-11 17:16:46
    1、首先确定是否有内存泄露及哪个程序造成。 1.1、内存泄露已弹出out of memory对话框的情况。 这种情况很简单,直接看对话框就知道是哪个应用的问题了。然后再分析该应用是否是因为内存泄露造成的out of memory...
  • nmon结果分析

    万次阅读 2016-04-11 20:16:59
    用nmon_analyser_hzt.xls等分析工具打开nmon结果文件,如果出现无法加载宏的提示,点击工具-宏-安全性,将安全及调至低,保存后,重新打开。 Ø Sys_summ页,为服务器资源使用率汇总 我们需求的主要数据为cpu,mem...
  • Xcode 内存分析工具使用

    千次阅读 2016-01-17 00:03:54
    Xcode 内存分析有静态分析和动态分析,静态分析主要是语法,会自动找出不合理的代码,提醒我们需要在某个地方优化,但是这个有的时候并不准确,该提醒的不提醒。 使用方法是 在Xcode 选择 Product ->Analyze就...
1 2 3 4 5 ... 20
收藏数 1,364,138
精华内容 545,655
关键字:

内存分析