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

在这里插入图片描述从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)

插件地址:
在这里插入图片描述本地连接

使用本地连接,在idea启动OOMTest.java,在DOS窗口输入jvisualvm,然后选择OOMTest,进入监控界面。
在这里插入图片描述导出heapdump、threaddump

先点击右侧堆dump,然后点击左侧的heapdump文件,将其保存到桌面。
在这里插入图片描述线程dump
先点击线程dump,然后点击左侧的threaddump,保存到桌面。
在这里插入图片描述导入heapdump、threaddump
点击文件,选择装入,找到文件位置,选择要导入的文件类型

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

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

点击#1856
在这里插入图片描述点击#569,发现Object数组里面存储的是Picture
在这里插入图片描述点击#887,发现Picture里面有byte[ ]
在这里插入图片描述
由此,我们遍发现了是OOMTest类中的main方法中的list对象中存储的Picture对象太多导致的内存占用过高。

导入threaddump
文件–>装入–>修改文件类型
在这里插入图片描述在这里插入图片描述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文件
在这里插入图片描述
如果是第一次打开,就选第一个,否则,选最后一个。
在这里插入图片描述

MAT菜单栏
在这里插入图片描述
Histogram(直方图)
在这里插入图片描述
寻找指定对象的GC ROOTS(只看强引用)
在这里插入图片描述也可以直接搜索
在这里插入图片描述查看所有的线程
下图中的local指的就是局部变量
在这里插入图片描述获得对象互相引用的关系
在这里插入图片描述
Leak Suspects(内存泄露分析)
在这里插入图片描述在这里插入图片描述想要看详情可以点击上图的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):
深堆是指对象的保留集中所有的对象的浅堆大小之和。
注意:浅堆指对象本身占用的内存,不包括其内部引用对象的大小。一个对象的深堆指只能通过该对象访问到的(直接或间接)所有对象的浅堆之和,即对象被回收后,可以释放的真实空间。

在这里插入图片描述在这里插入图片描述
在这里插入图片描述

支配树(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。

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

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

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

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

4)各种连接,如数据库连接、网络连接和IO连接等
各种连接,如数据库连接、网络连接和IO连接等。
在对数据库进行操作的过程中,首先需要建立与数据库的连接,当不再使用时,需要调用close方法来释放与数据库的连接。只有连接被关闭后,垃圾回收器才会回收对应的对象。否则,如果在访问数据库的过程中,对Connection、Statement或ResultSet不显性地关闭,将会造成大量的对象无法被回收,从而引起内存泄漏。
在这里插入图片描述5)变量不合理的作用域
在这里插入图片描述
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
 **/

在这里插入图片描述
实际工作中并不建议使用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子句:
在这里插入图片描述
FROM子句:
在这里插入图片描述
WHERE子句
在这里插入图片描述
内置对象与方法
在这里插入图片描述

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
跨平台,拥有多种操作系统的安装版本
在这里插入图片描述

5.2 安装与配置

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

5.2.1 JProfiler中配置IDEA

在这里插入图片描述
在这里插入图片描述在这里插入图片描述最终点击OK即可。

5.2.2 IDEA集成JProfiler

在idea中直接安装插件即可,如果下载不下来,当然也可以使用jar包安装插件。我的直接下载成功了,然后重启idea。
11版本的JProfiler插件直接链接了idea,不用再手动关联了。
在这里插入图片描述

5.3 具体使用
5.3.1 软件操作指南

在这里插入图片描述在这里插入图片描述

5.3.2 数据采集方式

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

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

注: JProfiler本身没有指出数据的采集类型,这里的采集类型是针对方法调用的采集类型。因为JProfiler的绝大多数核心功能都依赖方法调用采集的数据,所以可以直接认为是Profiler的数据采集类型。
在这里插入图片描述

5.3.3 遥感监测 Telemetries

总览
在这里插入图片描述内存
在这里插入图片描述

5.3.4 内存视图 Live Memory

在这里插入图片描述在这里插入图片描述在这里插入图片描述
分析内存泄露
什么情况下可能存在内存泄露?
在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

5.3.5 堆遍历 heap walker

1)、分析对象的引用
从all Objects跳转到heap walker
在这里插入图片描述在这里插入图片描述使用选中的对象
在这里插入图片描述看看谁引用了这个对象
在这里插入图片描述找到最终引用的位置

Shallow size、 Retained size、 Deep size

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

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

Deep(深) size:包含那些对象的大小。深大小与保留大小的区别在于深大小包含那些存在共享的对象,但是保留大小则不包括。
在这里插入图片描述在图表中展示
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
2)、导出hprof文件
在这里插入图片描述

5.3.6 cpu视图 cpu views

在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

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

在这里插入图片描述在这里插入图片描述
在这里插入图片描述

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启动程序
在这里插入图片描述发现内存直线上升
在这里插入图片描述然后在所有对象中标记一下,看看谁增长最快且不能被回收
在这里插入图片描述
然后在heap walker中分析
在这里插入图片描述接着在heap walker中选中该对象,并分析是谁在引用当前对象
在这里插入图片描述在这里插入图片描述定位到是谁在引用
在这里插入图片描述
翻看代码定位到MemoryLeak和Bean类,发现Bean类中定义了一个静态的list,该list在gc过程中无法被回收。可以考虑将其改为非静态或者在执行完成后将该list清空。 Bean.list.clear();
代码修改后内存回收如下图,说明list可以被回收掉了,内存上去了又下来了,不会一直上升。
在这里插入图片描述

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 基础指令

在这里插入图片描述

6.3.2 jvm相关

在这里插入图片描述dashboard -i 3000 -n 2:i是时间间隔,ms ; n是打印次数
在这里插入图片描述
thread
在这里插入图片描述在这里插入图片描述在这里插入图片描述heapdump --live /home/hello/1.hprof
在这里插入图片描述在这里插入图片描述

6.3.3 class/classloader相关

在这里插入图片描述sc
在这里插入图片描述

在这里插入图片描述sc com.atguigu.*
在这里插入图片描述sc -d -f com.atguigu.jvm.controller.TestController
在这里插入图片描述sm
在这里插入图片描述
在这里插入图片描述sm com.atguigu.jvm.controller.TestController:没带方法就会列出所有的方法
sm -d com.atguigu.jvm.controller.TestController test:带上方法名和-d会显示该方法的详细信息
在这里插入图片描述jad com.atguigu.jvm.controller.TestController test 反编译已加载的指定类
在这里插入图片描述在这里插入图片描述
在这里插入图片描述

6.3.4 monitor/watch/trace相关

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

trace com.test.study.TestController test
在这里插入图片描述
耗时最长的方法会被标红,然后重点分析该方法的代码,进行优化

诊断结束,执行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
在这里插入图片描述
在这里插入图片描述
参考博客

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

7 Java Misssion Control

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
在这里插入图片描述Java Flight Recorder
在这里插入图片描述
在这里插入图片描述
方式1:使用-XX:StartFlightRecording=参数
在这里插入图片描述
方式2:使用jcmd的JFR.*子命令
在这里插入图片描述
方式3:JMC的JFR插件
在这里插入图片描述
在这里插入图片描述

/**
 * -Xms600m -Xmx600m -XX:+UnlockCommercialFeatures -XX:+FlightRecorder
 * @author shkstart  shkstart@126.com
 * @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];
    }
}

在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述取样结束后会自动在JMC中打开
在这里插入图片描述在这里插入图片描述

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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值