JVM监控及诊断工具-GUI

1. 工具概述

JVM监控及诊断工具-GUI_第1张图片使用上一章命令行工具或组合能帮您获取目标Java应用性能相关的基础信息,但它们存在下列局限:
1)无法获取方法级别的分析数据,如方法间的调用关系、各方法的调用次数和调用时间
等(这对定位应用性能瓶颈至关重要)。
2)要求用户登录到目标Java应用所在的宿主机上,使用起来不是很方便。
3)分析数据通过终端输出,结果展示不够直观。

为此,JDK提供了一些内存泄漏的分析工具,如jconsole,jvisualvm等,用于辅助开发人员定位问题,但是这些工具很多时候并不足以满足快速定位的需求。所以这里我们介绍的工具相对多一些、丰富一些。

图形化综合诊断工具年
JDK自带的工具
jconsole:JDK自带的可视化监控工具。查看Java应用程序的运行概况、监控堆信息、永久区(或元空间)使用情况、类加载情况等
位置: jdk lbinljconsole.exe
Visual VM:Visual VM是一个工具,它提供了一个可视界面,用于查看Java虚拟机上运行的基于Java技术的应用程序的详细信息。
位置:jdk \binljvisualvm.exe
JMC:Java Mission Control,内置Java Flight Recorder。能够以极低的性能开销收集Java虚拟机的性能数据。

第三方工具
MAT: MAT(Memory Analyzer Tool)是基于Eclipse的内存分析工具,是一个快速、功能丰富的Java heap分析工具,它可以帮助我们查找内存泄漏和减少内存消耗。Eclipse的插件形式
Profiler:商业软件,需要付费。功能强大。与 VisualVM类似
Arthas:Alibaba开源的Java诊断工具。深受开发者喜爱。
Btrace:Java运行时追踪工具。可以在不停机的情况下,跟踪指定的方法调用、构造函数调用和系统内存等信息。

2. JConsole

JVM监控及诊断工具-GUI_第2张图片从Java5开始,在JDK中自带的java监控和管理控制台。
用于对VM中内存、线程和类等的监控,是一个基于JMX(java management extensions)的GUI性能监控工具。

官方教程:

3. Visual VM

基本概述(重点掌握)
Visual VM是一个功能强大的多合一故障诊断和性能监控的可视化工具。
它集成了多个JDK命令行工具,使用visual VM可用于显示虚拟机进程及进程的配置和环境信息
(jps,jinfo),监视应用程序的CPU、GC、堆、方法区及线程的信息(jstat、jstack)等,甚至代替JConsole。
在DK 6 Update 7以后,Visual VM便作为JDK的一部分发布(VisualVM 在JDK/bin目录下),即:它完全免费。|
此外,Visual VM也可以作为独立的软件安装:

首页

在jdk安装目录中找到jvisualvm.exe,然后双击执行即可。或打开DOS窗口,输入jvisualvm就可以打开该软件。
单独安装叫Visual VM,jdk自带叫jvisualvm。

安装插件
Visual VM的一大特点是支持插件扩展,并且插件安装非常方便。我们既可以通过离线下载插件文件*.nbm,然后在Plugin对话框的已下载页面下,添加已下载的插件。也可以在可用插件页面下,在线安装插件。(这里建议安装上:VisualGC)

插件地址:
JVM监控及诊断工具-GUI_第3张图片本地连接

使用本地连接,在idea启动OOMTest.java,在DOS窗口输入jvisualvm,然后选择OOMTest,进入监控界面。
JVM监控及诊断工具-GUI_第4张图片导出heapdump、threaddump

先点击右侧堆dump,然后点击左侧的heapdump文件,将其保存到桌面。
JVM监控及诊断工具-GUI_第5张图片线程dump
先点击线程dump,然后点击左侧的threaddump,保存到桌面。
JVM监控及诊断工具-GUI_第6张图片导入heapdump、threaddump
点击文件,选择装入,找到文件位置,选择要导入的文件类型

导入heapdump
JVM监控及诊断工具-GUI_第7张图片分析过程:十分重要
点击查找,找到最大的对象
JVM监控及诊断工具-GUI_第8张图片点击#84进入,发现ArrayList里面存储的就是#1856
JVM监控及诊断工具-GUI_第9张图片选中ArrayList,点击在线程中显示。
JVM监控及诊断工具-GUI_第10张图片直接定位到具体的类的方法:at com.atguigu.springcloud.jvm.OOMTest.main(OOMTest.java:16)
JVM监控及诊断工具-GUI_第11张图片如此便定位到了堆中最大的对象的位置。然后就可以去翻看代码,查看代码逻辑是否有问题。

配合下面的步骤,具体定位到原因。

点击#1856
JVM监控及诊断工具-GUI_第12张图片点击#569,发现Object数组里面存储的是Picture
JVM监控及诊断工具-GUI_第13张图片点击#887,发现Picture里面有byte[ ]
JVM监控及诊断工具-GUI_第14张图片
由此,我们遍发现了是OOMTest类中的main方法中的list对象中存储的Picture对象太多导致的内存占用过高。

导入threaddump
文件–>装入–>修改文件类型
JVM监控及诊断工具-GUI_第15张图片JVM监控及诊断工具-GUI_第16张图片jvisualvm分析线程dump好像不怎么方便的样子,暂且点到为止。

4. Eclipse MAT

4.1 基本概述

MAT(Memory Analyzer Tool)工具是一款功能强大的Java堆内存分析器。可以用于查找内存泄漏以及查看内存消耗情况。
MAT是基于Eclipse开发的,不仅可以单独使用,还可以作为插件的形式嵌入在Eclipse中使用。是一款免费的性能分析工具,使用起来非常方便。

下载:

只要确保机器上装有JDK并配置好相关的环境变量,MAT可正常启动。还可以在Eclipse中以插件的方式安装:

4.2 获取dump文件

dump文件内容
MAT可以分析heap dump文件。在进行内存分析时,只要获得了反映当前设备内存映像的hprof文件,通过MAT打开就可以直观地看到当前的内存信息。
一般说来,这些内存信息包含:
所有的对象信息,包括对象实例、成员变量、存储于栈中的基本类型值和存储于堆中的其他对象的引用值。
所有的类信息,包括classloader、类名称、父类、静态变量等.
GCRoot到所有的这些对象的引用路径
线程信息,包括线程的调用栈及此线程的线程局部变量(TLS)

说明1:
MAT不是一个万能工具,它并不能处理所有类型的堆存储文件。但是比较主流的厂家和格式,例如Sun,HP,SAP所采用的 HPROF 二进制堆存储文件,以及IBM的 PHD堆存储文件等都能被很好的解析。
说明2:
最吸引人的还是能够快速为开发人员生成内存泄漏报表,方便定位问题和分析问题。虽然MAT有如此强大的功能,但是内存分析也没有简单到一键完成的程度,很多内存问题还是需要我们从MAT展现给我们的信息当中通过经验和直觉来判断才能发现。

获取dump文件
方法一:通过前一章介绍的 jmap工具生成,可以生成任意一个java进程的dump文件
方法二:通过配置JVM参数生成。
选项"-XX:+HeapDumpOnoutOfMemoryError”或"-XX:+HeapDumpBeforeFullGC"
选项"-XX :HeapDumpPath"所代表的含义就是当程序出现OutOfMemory时,将会在相应的目录下生成一份dump文件。如果不指定选项“-XX:HeapDumpPath”则在当前目录下生成dump文件。
对比:考虑到生产环境中几乎不可能在线对其进行分析,大都是采用离线分析,因此使用jmap+MAT工具是最常见的组合。
方法三:使用VisualVM可以导出堆dump文件
方法四:使用MAT既可以打开一个已有的堆快照,也可以通过MAT直接从活动Java程序中导出堆快照。该功能将借助jps列出当前正在运行的Java 进程,以供选择并获取快照。

4.3 分析堆dump文件

打开堆dump文件
JVM监控及诊断工具-GUI_第17张图片
如果是第一次打开,就选第一个,否则,选最后一个。
JVM监控及诊断工具-GUI_第18张图片

MAT菜单栏
JVM监控及诊断工具-GUI_第19张图片
Histogram(直方图)
JVM监控及诊断工具-GUI_第20张图片
寻找指定对象的GC ROOTS(只看强引用)
JVM监控及诊断工具-GUI_第21张图片也可以直接搜索
在这里插入图片描述查看所有的线程
下图中的local指的就是局部变量
JVM监控及诊断工具-GUI_第22张图片获得对象互相引用的关系
JVM监控及诊断工具-GUI_第23张图片
Leak Suspects(内存泄露分析)
JVM监控及诊断工具-GUI_第24张图片JVM监控及诊断工具-GUI_第25张图片想要看详情可以点击上图的Details>>

浅堆与深堆
浅堆(Shallow Heap)是指一个对象所消耗的内存。在32位系统中,一个对象引用会占据4个字节,一个int类型会占据4个字节,long型变量会占据8个字节,每个对象头需要占用8个字节。根据堆快照格式不同,对象的大小可能会向8字节进行对齐。

以String为例: 2个int值共占8字节,对象引用占用4字节,对象头8字节,合计20字节,
故占24字节。(jdk7中)
int hash32 0
int hash 0
ref value C:Users\Administrat
这24字节为String对象的浅堆大小。它与String的value实际取值无关,无论字符串长度如何,浅堆大小始终是24字节。

保留集(Retained Set):
对象A的保留集指当对象A被垃圾回收后,可以被释放的所有的对象集合(包括对象A本身),即对象A的保留集可以被认为是只能通过对象A被直接或间接访问到的所有对象的集合。通俗地说,就是指仅被对象A所持有的对象的集合。

深堆(Retained Heap):
深堆是指对象的保留集中所有的对象的浅堆大小之和。
注意:浅堆指对象本身占用的内存,不包括其内部引用对象的大小。一个对象的深堆指只能通过该对象访问到的(直接或间接)所有对象的浅堆之和,即对象被回收后,可以释放的真实空间。

JVM监控及诊断工具-GUI_第26张图片JVM监控及诊断工具-GUI_第27张图片
JVM监控及诊断工具-GUI_第28张图片

支配树(Dominator Tree)
支配树的概念源自图论。
MAT提供了一个称为支配树(Dominator Tree)的对象图。支配树体现了对象实例间的支配关系。在对象引用图中,所有指向对象B的路径都经过对象A,则认为对象A支配对象B。如果对象A是离对象B最近的一个支配对象,则认为对象A为对象B的直接支配者。支配树是基于对象间的引用图所建立的,它有以下基本性质:
对象A的子树(所有被对象A支配的对象集合)表示对象A的保留集(retained set),即深堆。
如果对象A支配对象B,那么对象A的直接支配者也支配对象B。
支配树的边与对象引用图的边不直接对应。

如下图所示:左图表示对象引用图,右图表示左图所对应的支配树。对象A和B由根对象直接支配,由于在到对象c的路径中,可以经过A,也可以经过B,因此对象c的直接支配者也是根对象。对象F与对象D相互引用,因为到对象F的所有路径必然经过对象D,因此,对象D是对象F的直接支配者。而到对象D的所有路径中,必然经过对象c,即使是从对象F到对象D的引用,从根节点出发,也是经过对象C的,所以,对象D的直接支配者为对象C。

JVM监控及诊断工具-GUI_第29张图片注意:
从“对象引用图“到”支配树”
支配者:如果要到达对象B,必须经过对象A,那么对象A就是对象B的支配者,可以想到支配者大于等于1。
直接支配者:在支配者中距离对象B最近的对象A就是对象B的直接支配者,直接支配者不一定就是对象B的上一级,直接支配者只有一个。
支配树是怎么画?支配树中的对象与对象之间的关系就是直接支配关系,也就是上一级是下一级的直接支配者,只要按照这样的方式来作图,肯定从“对象引用图“到”支配树”。JVM监控及诊断工具-GUI_第30张图片
内存泄露
JVM监控及诊断工具-GUI_第31张图片
JVM监控及诊断工具-GUI_第32张图片
内存泄漏与内存溢出的关系:
内存泄漏( memory leak )
申请了内存用完了不释放,比如一共有1024M 的内存,分配了512M的内存一直不回收,那么可以用的内存只有512M了,仿佛泄露掉了一部分;通俗一点讲的话,内存泄漏就是【占着茅坑不拉shil. lI

内存溢出(out of memory)
申请内存时,没有足够的内存可以使用;
通俗一点儿讲,一个厕所就三个坑,有两个占着茅坑不走的(内存泄漏)﹐剩下最后一个坑,厕所表示接待压力很大,这时候一下子来了两个人,坑位(内存)就不够了,内存泄漏变成内存溢出了。
可见,内存泄漏和内存溢出的关系:内存泄漏的增多,最终会导致内存溢出。量变引起质变

泄漏的分类
经常发生:发生内存泄露的代码会被多次执行,每次执行,泄露一块内存;
偶然发生:在某些特定情况下才会发生
一次性:发生内存泄露的方法只会执行一次;
隐式泄漏:一直占着内存不释放,直到执行结束;严格的说这个不算内存泄漏,因为最终释放掉了,但是如果执行时间特别长,也可能会导致内存耗尽。
JVM监控及诊断工具-GUI_第33张图片Java中内存泄露的8种情况
1)静态集合类
静态集合类,如HashMap、LinkedList等等。如果这些容器为静态的,那么它们的生命周期与JVM程序一致,则容器中的对象在程序结束之前将不能被释放,从而造成内存泄漏。简单而言,长生命周期的对象持有短生命周期对象的引用,尽管短生命周期的对象不再使用,但是因为长生命周期对象持有它的引用而导致不能被回收。
JVM监控及诊断工具-GUI_第34张图片2)单例模式
单例模式,和静态集合导致内存泄露的原因类似,因为单例的静态特性,它的生命周期和JVM的生命周期一样长,所以如果单例对象如果持有外部对象的引用。那么这个外部对象也不会被回收,那么就会造成内存泄漏。

3)内部类持有外部类
内部类持有外部类,如果一个外部类的实例对象的方法返回了一个内部类的实例对象。这个内部类对象被长期引用了,即使那个外部类实例对象不再被使用,但由于内部类持有外部类的实例对象,这个外部类对象将不会被垃圾回收,这也会造成内存泄漏。

4)各种连接,如数据库连接、网络连接和IO连接等
各种连接,如数据库连接、网络连接和IO连接等。
在对数据库进行操作的过程中,首先需要建立与数据库的连接,当不再使用时,需要调用close方法来释放与数据库的连接。只有连接被关闭后,垃圾回收器才会回收对应的对象。否则,如果在访问数据库的过程中,对Connection、Statement或ResultSet不显性地关闭,将会造成大量的对象无法被回收,从而引起内存泄漏。
JVM监控及诊断工具-GUI_第35张图片5)变量不合理的作用域
JVM监控及诊断工具-GUI_第36张图片
6)改变哈希值
改变哈希值,当一个对象被存储进HashSet集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段了。|
否则,对象修改后的哈希值与最初存储进HashSet集合中时的哈希值就不同了,在这种情况下,即使在contains方法使用该对象的当前引用作为的参数去HashSet集合中检索对象,也将返回找不到对象的结果,这也会导致无法从HashSet集合中单独删除当前对象,造成内存泄漏。
这也是string为什么被设置成了不可变类型,我们可以放心地把 string存入 HashSet,或者把String 当做HashMap 的key值;
当我们想把自己定义的类保存到散列表的时候,需要保证对象的 hashCode不可变。

/**
 * 演示内存泄漏
 *
 * @author shkstart
 * @create 14:43
 */
public class ChangeHashCode {
    public static void main(String[] args) {
        HashSet set = new HashSet();
        Person p1 = new Person(1001, "AA");
        Person p2 = new Person(1002, "BB");

        set.add(p1);
        set.add(p2);

        p1.name = "CC";//导致了内存的泄漏
        set.remove(p1); //删除失败

        System.out.println(set);

        set.add(new Person(1001, "CC"));
        System.out.println(set);

        set.add(new Person(1001, "AA"));
        System.out.println(set);

    }
}

class Person {
    int id;
    String name;

    public Person(int id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Person)) return false;

        Person person = (Person) o;

        if (id != person.id) return false;
        return name != null ? name.equals(person.name) : person.name == null;
    }

    @Override
    public int hashCode() {
        int result = id;
        result = 31 * result + (name != null ? name.hashCode() : 0);
        return result;
    }

    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

7)缓存泄露
内存泄漏的另一个常见来源是缓存,一旦你把对象引用放入到缓存中,他就很容易遗忘。比如:之前项目在一次上线的时候,应用启动奇慢直到夯死,就是因为代码中会加载一个表中的数据到缓存(内存)中,测试环境只有几百条数据,但是生产环境有几百万的数据。
对于这个问题,可以使用WeakHashMap代表缓存,此种Map的特点是,当除了自身有对key的引用外,此key没有其他引用那么此map会自动丢弃此值。

/**
 * 演示内存泄漏
 *
 * @author shkstart
 * @create 14:53
 */
public class MapTest {
    static Map wMap = new WeakHashMap();
    static Map map = new HashMap();

    public static void main(String[] args) {
        init();
        testWeakHashMap();
        testHashMap();
    }

    public static void init() {
        String ref1 = new String("obejct1");
        String ref2 = new String("obejct2");
        String ref3 = new String("obejct3");
        String ref4 = new String("obejct4");
        wMap.put(ref1, "cacheObject1");
        wMap.put(ref2, "cacheObject2");
        map.put(ref3, "cacheObject3");
        map.put(ref4, "cacheObject4");
        System.out.println("String引用ref1,ref2,ref3,ref4 消失");

    }

    public static void testWeakHashMap() {

        System.out.println("WeakHashMap GC之前");
        for (Object o : wMap.entrySet()) {
            System.out.println(o);
        }
        try {
            System.gc();
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("WeakHashMap GC之后");
        for (Object o : wMap.entrySet()) {
            System.out.println(o);
        }
    }

    public static void testHashMap() {
        System.out.println("HashMap GC之前");
        for (Object o : map.entrySet()) {
            System.out.println(o);
        }
        try {
            System.gc();
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("HashMap GC之后");
        for (Object o : map.entrySet()) {
            System.out.println(o);
        }
    }

}
/**
 * 结果
 * String引用ref1,ref2,ref3,ref4 消失
 * WeakHashMap GC之前
 * obejct2=cacheObject2
 * obejct1=cacheObject1
 * WeakHashMap GC之后
 * HashMap GC之前
 * obejct4=cacheObject4
 * obejct3=cacheObject3
 * Disconnected from the target VM, address: '127.0.0.1:51628', transport: 'socket'
 * HashMap GC之后
 * obejct4=cacheObject4
 * obejct3=cacheObject3
 **/

JVM监控及诊断工具-GUI_第37张图片
实际工作中并不建议使用WeakHashMap,可以使用HashMap,限制HashMap的数量,以及不使用了及时清空即可。

8)监听器和回调
内存泄漏第三个常见来源是监听器和其他回调,如果客户端在你实现的API中注册回调,却没有显示的取消,那么就会积聚。
需要确保回调立即被当作垃圾回收的最佳方法是只保存它的弱引用,例如将他们保存成为weakHashMap护的键。

内存泄露案例分析

public class Stack {
    private Object[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    public Stack() {
        elements = new Object[DEFAULT_INITIAL_CAPACITY];
    }

    public void push(Object e) { //入栈
        ensureCapacity();
        elements[size++] = e;
    }
    //存在内存泄漏
//    public Object pop() { //出栈
//        if (size == 0)
//            throw new EmptyStackException();
//        return elements[--size];
//    }

    public Object pop() {
        if (size == 0)
            throw new EmptyStackException();
        Object result = elements[--size];
        elements[size] = null;
        return result;
    }

    private void ensureCapacity() {
        if (elements.length == size)
            elements = Arrays.copyOf(elements, 2 * size + 1);
    }
}

上述程序并没有明显的错误,但是这段程序有一个内存泄漏,随着GC活动的增加,或者内存占用的不断增加,程序性能的降低就会表现出来,严重时可导致内存泄漏,但是这种失败情况相对较少。代码的主要问题在pop函数。
当进行大量的pop操作时,由于引用未进行置空,gc是不会释放的。
如果栈先增长,然后收缩,那么从栈中弹出的对象将不会被当作垃圾回收,即使程序不再使用栈中的这些队象,他们也不会回收,因为栈中仍然保存这对象的引用,俗称过期引用,这个内存泄露很隐蔽。

所以当对象出栈后及时将引用置空。elements[size] = null;防止内存泄露。

支持使用OQL语言查询对象信息
SELECT子句:
JVM监控及诊断工具-GUI_第38张图片
FROM子句:
JVM监控及诊断工具-GUI_第39张图片
WHERE子句
JVM监控及诊断工具-GUI_第40张图片
内置对象与方法
JVM监控及诊断工具-GUI_第41张图片

5. JProfiler

5.1 基本概述

在运行Java的时候有时候想测试运行时占用内存情况,这时候就需要使用测试工具查看了。在
eclipse里面有 Eclipse Memory Analyzer too1(MAT)插件可以测试,而在IDEA中也有这么一个插件,就是JProfiler。
JProfiler是由 ej-technologies公司开发的一款 Java应用性能诊断工具。功能强大,但是收费。

官网下载地址:

特点:
使用方便、界面操作友好(简单且强大)·对被分析的应用影响小(提供模板).
CPU,Thread ,Memory分析功能尤其强大
支持对jdbc , noSql,jsp, servlet,socket等进行分析
支持多种模式(离线,在线)的分析
支持监控本地、远程的JVM
跨平台,拥有多种操作系统的安装版本
JVM监控及诊断工具-GUI_第42张图片

5.2 安装与配置

下载与安装
使用特别版直接安装,然后使用注册码激活。

5.2.1 JProfiler中配置IDEA

JVM监控及诊断工具-GUI_第43张图片
JVM监控及诊断工具-GUI_第44张图片在这里插入图片描述最终点击OK即可。

5.2.2 IDEA集成JProfiler

在idea中直接安装插件即可,如果下载不下来,当然也可以使用jar包安装插件。我的直接下载成功了,然后重启idea。
11版本的JProfiler插件直接链接了idea,不用再手动关联了。
JVM监控及诊断工具-GUI_第45张图片

5.3 具体使用

5.3.1 软件操作指南

JVM监控及诊断工具-GUI_第46张图片JVM监控及诊断工具-GUI_第47张图片

5.3.2 数据采集方式

JProfier数据采集方式分为两种:Sampling(样本采集)和Instrumentation(重构模式)
Instrumentation:这是Profiler全功能模式。在class加载之前,JProfier把相关功能代码写入到需要分析的class的bytecode中,对正在运行的jvm有一定影响。
优点:功能强大。在此设置中,调用堆栈信息是准确的。
缺点:若要分析的class较多,则对应用的性能影响较大,CPU开销可能很高(取决于Filter的控制)。因此使用此模式一般配合Filter使用,只对特定的类或包进行分析

Sampling:类似于样本统计,每隔一定时间(5ms)将每个线程栈中方法栈中的信息统计出来。
优点:对CPU的开销非常低,对应用影响小(即使你不配置任何Filter)·缺点:一些数据/特性不能提供(例如:方法的调用次数、执行时间)

注: JProfiler本身没有指出数据的采集类型,这里的采集类型是针对方法调用的采集类型。因为JProfiler的绝大多数核心功能都依赖方法调用采集的数据,所以可以直接认为是Profiler的数据采集类型。
JVM监控及诊断工具-GUI_第48张图片

5.3.3 遥感监测 Telemetries

总览
JVM监控及诊断工具-GUI_第49张图片内存
JVM监控及诊断工具-GUI_第50张图片

5.3.4 内存视图 Live Memory

JVM监控及诊断工具-GUI_第51张图片JVM监控及诊断工具-GUI_第52张图片
分析内存泄露
什么情况下可能存在内存泄露?
JVM监控及诊断工具-GUI_第53张图片JVM监控及诊断工具-GUI_第54张图片JVM监控及诊断工具-GUI_第55张图片

5.3.5 堆遍历 heap walker

1)、分析对象的引用
从all Objects跳转到heap walker
JVM监控及诊断工具-GUI_第56张图片使用选中的对象
JVM监控及诊断工具-GUI_第57张图片看看谁引用了这个对象
JVM监控及诊断工具-GUI_第58张图片找到最终引用的位置

Shallow size、 Retained size、 Deep size

Shallow(浅) size:就是对象本身占用内存的大小,不包含对其他对象的引用,也就是对象头加成员变量(不是成员变量的值)的总和。

Retained(保留) size:是该对象自己的shallow size,加上从该对象能直接或间接访问到对象的shallow size之和。换句话说,retained size是该对象被GC之后所能回收到内存的总和。

Deep(深) size:包含那些对象的大小。深大小与保留大小的区别在于深大小包含那些存在共享的对象,但是保留大小则不包括。
在图表中展示

JVM监控及诊断工具-GUI_第59张图片
JVM监控及诊断工具-GUI_第60张图片
2)、导出hprof文件

5.3.6 cpu视图 cpu views

JVM监控及诊断工具-GUI_第61张图片JVM监控及诊断工具-GUI_第62张图片JVM监控及诊断工具-GUI_第63张图片JVM监控及诊断工具-GUI_第64张图片

5.3.7 线程视图 threads、监视器&锁 Monitors&locks

JVM监控及诊断工具-GUI_第65张图片
JVM监控及诊断工具-GUI_第66张图片

5.4 案例分析

代码

public class MemoryLeak {

    public static void main(String[] args) {
        while (true) {
            ArrayList beanList = new ArrayList();
            for (int i = 0; i < 500; i++) {
                Bean data = new Bean();
                data.list.add(new byte[1024 * 10]);//10kb
                beanList.add(data);
            }
            try {
                TimeUnit.MILLISECONDS.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

class Bean {
    int size = 10;
    String info = "hello,atguigu";
//     ArrayList list = new ArrayList();
    static ArrayList list = new ArrayList();
}

使用profiler启动程序
JVM监控及诊断工具-GUI_第67张图片发现内存直线上升
JVM监控及诊断工具-GUI_第68张图片然后在所有对象中标记一下,看看谁增长最快且不能被回收
JVM监控及诊断工具-GUI_第69张图片
然后在heap walker中分析
JVM监控及诊断工具-GUI_第70张图片接着在heap walker中选中该对象,并分析是谁在引用当前对象
JVM监控及诊断工具-GUI_第71张图片JVM监控及诊断工具-GUI_第72张图片定位到是谁在引用
JVM监控及诊断工具-GUI_第73张图片
翻看代码定位到MemoryLeak和Bean类,发现Bean类中定义了一个静态的list,该list在gc过程中无法被回收。可以考虑将其改为非静态或者在执行完成后将该list清空。 Bean.list.clear();
代码修改后内存回收如下图,说明list可以被回收掉了,内存上去了又下来了,不会一直上升。
JVM监控及诊断工具-GUI_第74张图片

6、Arthas

6.1 概述

这两款工具在业界知名度也比较高,他们的优点是可以图形界面上看到各维度的性能数据,使用者根据这些数据进行综合分析,然后判断哪里出现了性能问题。
但是这两款工具也有个缺点,都必须在服务端项目进程中配置相关的监控参数。然后工具通过远程连接到项目进程,获取相关的数据。这样就会带来一些不便,比如线上环境的网络是隔离的,本地的监控工具根本连不上线上环境。并且类似于Jprofiler这样的商业工具,是需要付费的。
那么有没有一款工具不需要远程连接,也不需要配置监控参数,同时也提供了丰富的性能监控数据呢?
今天跟大家介绍一款阿里巴巴开源的性能分析神器Arthas(阿尔萨斯)

Arthas(阿尔萨斯)是Alibaba开源的Java诊断工具,深受开发者喜爱。在线排查问题,无需重启;动态跟踪Java代码;实时监控VM状态。
Arthas支持JDK 6+,支持Linux/Mac/Windows,采用命令行交互模式,同时提供丰富的Tab自动补全功能,进一步方便进行问题的定位和诊断。
当你遇到以下类似问题而束手无策时,Arthas可以帮助你解决:
这个类从哪个jar包加载的?为什么会报各种类相关的Exception?
我改的代码为什么没有执行到?难道是我没commit?分支搞错了?
遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?
线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!
是否有一个全局视角来查看系统的运行状况?
有什么办法可以监控到JVM的实时运行状态?怎么快速定位应用的热点,生成火焰图?

基于哪些工具开发而来
greys-anatomy: Arthas代码基于Greys二次开发而来,非常感谢Greys之前所有的工作,以及Greys原作者对Arthas提出的意见和建议!
termd: Arthas的命令行实现基于termd开发,是一款优秀的命令行程序开发框架,感谢termd提供了优秀的框架。
crash: Arthas的文本渲染功能基于crash中的文本渲染功能开发,可以从这里看到源码,感谢crash在这方面所做的优秀工作。
cli: Arthas的命令行界面基于vert.x提供的cli库进行开发,感谢vert.x在这方面做的优秀工作。
compiler Arthas里的内存编绎器代码来源
Apache Commons Net Arthas里的Telnet Client代码来源
JavaAgent:运行在 main方法之前的拦截器,它内定的方法名叫 premain ,也就是说先执行
premain方法然后再执行main方法
ASM:一个通用的Java字节码操作和分析框架。它可以用于修改现有的类或直接以二进制形式动态生成类。ASM提供了一些常见的字节码转换和分析算法,可以从它们构建定制的复杂转换和代码分析工具。ASM提供了与其他Java字节码框架类似的功能,但是主要关注性能。因为它被设计和实现得尽可能小和快,所以非常适合在动态系统中使用(当然也可以以静态方式使用,例如在编译器中)

官方文档

6.2 安装与使用

安装方式一:可以直接在Linux上通过命令下载
可以在官方Github 上进行下载,如果速度较慢,可以尝试国内的码云 Gitee下载。

github下载

Gitee下载

卸载:
在 Linux/Unix/Mac平台删除下面文件:
rm -rf ~/ .arthas/
rm -rf ~/logs/arthas
windows平台直接删除user home下面的.arthas和logs/arthas目录

工程目录:
arthas-agent:基于JavaAgent技术的代理
bin:一些启动脚本
arthas-boot: Java版本的一键安装启动脚本
arthas-client: telnet client代码
arthas-common:一些共用的工具类和枚举类
arthas-core:核心库,各种arthas命令的交互和实现
arthas-demo:示例代码
arthas-memorycompiler:内存编绎器代码,Fork fromhttps://github.com/skalogs/SkaETL/tree/master/compiler
arthas-packaging: maven打包相关的
arthas-site: arthas站点
arthas-spy:编织到目标类中的各个切面static:静态资源
arthas-testcase:测试

启动
Arthas只是一个java程序,所以可以直接用java -jar 运行。
执行成功后,arthas提供了一种命令行方式的交互方式,arthas会检测当前服务器上的Java进程,并将进程列表展示出来,用户输入对应的编号进行选择,然后回车。

比如:方式1:
java -jar arthas-boot.jar
选择进程(输入[]内编号(不是PID)回车)
[INFO] arthas-boot version: 3.1.4
[INFO] Found existing java process,please choose one and hit RETURN.
*[1]:11616 com.ArthasI
 [2]:8676
 [3]:16200 org.jetbrains.jps.cmdline.Launcher
 [4]: 21032 org.jetbrains.idea.maven.server.RemoteMavenServer
方式2:运行时选择Java进程 PID
java -jar arthas-boot.jar [PID]

退出
最后一行[arthas@7457]$,说明打开进入了监控客户端,在这里就可以执行相关命令进行查看了。
使用quit/exit:退出当前客户端
使用stop/shutdown:关闭arthas服务端,并退出所有客户端。

查看日志:cat ~/logs/arthas/arthas.log
查看帮助:java -jar arthas-boot.jar -h

6.3 相关诊断指令

6.3.1 基础指令

JVM监控及诊断工具-GUI_第75张图片

6.3.2 jvm相关

JVM监控及诊断工具-GUI_第76张图片dashboard -i 3000 -n 2:i是时间间隔,ms ; n是打印次数
JVM监控及诊断工具-GUI_第77张图片
thread
JVM监控及诊断工具-GUI_第78张图片在这里插入图片描述JVM监控及诊断工具-GUI_第79张图片heapdump --live /home/hello/1.hprof
JVM监控及诊断工具-GUI_第80张图片在这里插入图片描述

6.3.3 class/classloader相关

JVM监控及诊断工具-GUI_第81张图片sc
JVM监控及诊断工具-GUI_第82张图片

JVM监控及诊断工具-GUI_第83张图片sc com.atguigu.*
在这里插入图片描述sc -d -f com.atguigu.jvm.controller.TestController
JVM监控及诊断工具-GUI_第84张图片sm
JVM监控及诊断工具-GUI_第85张图片
JVM监控及诊断工具-GUI_第86张图片sm com.atguigu.jvm.controller.TestController:没带方法就会列出所有的方法
sm -d com.atguigu.jvm.controller.TestController test:带上方法名和-d会显示该方法的详细信息
JVM监控及诊断工具-GUI_第87张图片jad com.atguigu.jvm.controller.TestController test 反编译已加载的指定类
JVM监控及诊断工具-GUI_第88张图片JVM监控及诊断工具-GUI_第89张图片
JVM监控及诊断工具-GUI_第90张图片

6.3.4 monitor/watch/trace相关

JVM监控及诊断工具-GUI_第91张图片trace指令十分常用,比如某个接口响应迟钝,排查一下性能瓶颈在哪?
启动arthas
java -jar arthas-boot.jar
在这里插入图片描述输入想要监控的服务对应的中括号中的数字

trace com.test.study.TestController test
JVM监控及诊断工具-GUI_第92张图片
耗时最长的方法会被标红,然后重点分析该方法的代码,进行优化

诊断结束,执行stop命令,会还原所有被增强过的类。

6.3.5 profiler/火焰图

profiler start:启动 profiler
profiler getSamples:获取已采集的 sample 的数量
profiler status:查看 profiler 状态
profiler stop --file /home/hello/1.html:停止 profiler,默认情况下,结果文件是html格式,也可以用–format参数指定。或在–file参数里用文件名指名格式。
sz 1.html:下载1.html并保存到桌面
使用浏览器打开1.html
JVM监控及诊断工具-GUI_第93张图片
JVM监控及诊断工具-GUI_第94张图片
参考博客

火焰图,简单通过x轴横条宽度来度量时间指标,y轴代表线程栈的层次。

7 Java Misssion Control

JVM监控及诊断工具-GUI_第95张图片
JVM监控及诊断工具-GUI_第96张图片
JVM监控及诊断工具-GUI_第97张图片JVM监控及诊断工具-GUI_第98张图片
JVM监控及诊断工具-GUI_第99张图片
JVM监控及诊断工具-GUI_第100张图片Java Flight Recorder
JVM监控及诊断工具-GUI_第101张图片
JVM监控及诊断工具-GUI_第102张图片
方式1:使用-XX:StartFlightRecording=参数
JVM监控及诊断工具-GUI_第103张图片
方式2:使用jcmd的JFR.*子命令
JVM监控及诊断工具-GUI_第104张图片
方式3:JMC的JFR插件
JVM监控及诊断工具-GUI_第105张图片
JVM监控及诊断工具-GUI_第106张图片

/**
 * -Xms600m -Xmx600m -XX:+UnlockCommercialFeatures -XX:+FlightRecorder
 * @author shkstart  [email protected]
 * @create 2020  21:12
 */
public class OOMTest {
    public static void main(String[] args) {
        ArrayList<Picture> list = new ArrayList<>();
        while(true){
            try {
                Thread.sleep(120);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            list.add(new Picture(new Random().nextInt(1024 * 1024)));
        }
    }
}

class Picture{
    private byte[] pixels;

    public Picture(int length) {
        this.pixels = new byte[length];
    }
}

JVM监控及诊断工具-GUI_第107张图片JVM监控及诊断工具-GUI_第108张图片JVM监控及诊断工具-GUI_第109张图片JVM监控及诊断工具-GUI_第110张图片JVM监控及诊断工具-GUI_第111张图片取样结束后会自动在JMC中打开
JVM监控及诊断工具-GUI_第112张图片JVM监控及诊断工具-GUI_第113张图片

注:本文是学习 尚硅谷宋红康JVM全套教程(详解java虚拟机)所做笔记。

你可能感兴趣的:(#,JVM,MAT,JMC,Visual,VM,JProfiler,Arthas)