22、JVM实战 - 性能监控与调优(三) -- JVM监控及诊断工具--GUI篇

1.工具概述

 

1.1 图形化总和诊断工具:

1、 JDK自带的工具;
 
2、 第三方工具;
 

2. jConsole(了解)

  • 从java5开始,在JDK中自带的java监控和管理控制台。
  • 用于对JVM中内存、线程和类等的监控,是一个基于JMX(java management extensions)的GUI性能监控工具。

2.1 启动

  • jdk/bin目录下,启动jconsole.exe命令即可
  • 不需要使用jps命令来查询

2.2 三种连接方式

  • Local:
    console连接一个正在本地系统运行的JVM,并且执行程序的和运行Jconsole的需要是同一个用户。Jconsole使用文件系统的授权通过RMI连接器连接到平台的MBean服务器上。这种从本地连接的监控能力只有Sun的JDK具有
  • Remote:
    使用下面的URL通过RMI连接器连接到一个JMX代理,service:jmx:rmi:///jndi/rmi://hostName:portNum/jmxrmi。JConsole为建立连接,需要在环境变量中设置mx.remote.credentials来指定用户名和密码,从而进行授权。
  • Advanced:
    使用一个特殊的URL连接JMX代理。一般情况使用自己定制的连接器而不是RMI提供的连接器来连接JMX代理,或者是一个使用JDK1.4的实现了JMX和JMX Rmote的应用

2.3 作用:

1、 JVM运行概览:;
 
2、 内存;
 
3、 线程;
 
4、 类:;
 
5、 检测死锁:;
 

3. Visual VM

3.1 概述

  • Visual VM是一个功能强大的多合一故障诊断和性能监控的可视化工具
  • 它集成了多个JDK命令行工具,使用Visual VM可用于显示虚拟机进程及进程的配置和环境信息(jps,jinfo),监视应用程序的CPU、GC、堆、方法区及线程的信息(jstat、jstack)等,甚至代替JConsole
  • 在JDK6 update7以后,Visual VM便作为JDK的一部分发布(JDK/bin),即完全免费
  • 此外,Visual VM也可以作为独立的软件安装
  • 也可以在IDEA中安装,然后配置IDEA中visual vm的位置,绑定使用

3.2 插件的安装

1、 方式一:网络下载;
 
2、 方式二:软件的工具栏->插件中直接下载;
 

3.3 连接方式:

 

3.4 主要功能

具体的使用可以看视频、或者自己操作软件学习与使用
 

1、 概述:(jinfo);
 
2、 监视;
 
3、 线程;
 
4、 GC;
 

3.3.1 生成与分析dump文件:

VisualVM中监视和线程的右上角都有Dump按钮,可以对当前JVM的情况进行dump,dump下来后可以点击文件->装入,进行dump文件分析,同时还可以比较两个不同时间段的dump文件

这里不多展示,但是较为重要,记得自己点击用着看看

3.3.2 CPU与内存抽样

可以对某一时刻的CPU和内存进行快照,然后查看CPU突然飙高的原因,以及内存中类过多的问题。
 

4. Eclipse MAT

4.1 基本概述:

主要作用:分析dump文件

 
 

4.2 获取堆dump文件

4.2.1 dump文件内容

 

4.2.2 两点说明

 

4.2.3 获取dump文件

 

4.3 分析堆dump文件

4.3.1 histogram:直方图

主要列举的内存中加载的类的个数,以及占用的浅堆及深堆的大小

 

点击具体的类可以看到:

 

分组:

 

排查GC roots

 
 
比较基于b跟a的dump文件的区别:
 

4.3.2 thread overview

可用于分析有可能出现内存泄漏的线程

两个用处:

1、 查看系统中的java线程;
2、 查看局部变量的信息;

系统中的线程:
 

局部变量:
 
 

分析可能出现的问题:
 

4.3.3 获取对象相互引用的关系

 

incoming:被谁引用
 
outcoming:引用过谁
 

4.3.4 浅堆和深堆(shallow heap和retained heap)

1、 浅堆:;
 
2、 深堆:当前对象的浅堆大小+所有只由他触及的对象的浅堆大小之和;
 
3、 补充:对象实际大小(对象所能触及的浅堆大小之和)![ ][nbsp37];

4.3.5 案例分析

代码:

/**
 * 有一个学生浏览网页的记录程序,它将记录 每个学生访问过的网站地址。
 * 它由三个部分组成:Student、WebPage和StudentTrace三个类
 *
 * 获取dump文件:-XX:+HeapDumpBeforeFullGC -XX:HeapDumpPath=c:\code\student.hprof
 * 
 */
public class StudentTrace {

    static List<WebPage> webpages = new ArrayList<WebPage>();

    public static void createWebPages() {

        for (int i = 0; i < 100; i++) {

            WebPage wp = new WebPage();
            wp.setUrl("http://www." + Integer.toString(i) + ".com");
            wp.setContent(Integer.toString(i));
            webpages.add(wp);
        }
    }

    public static void main(String[] args) {

        createWebPages();//创建了100个网页
        //创建3个学生对象
        Student st3 = new Student(3, "Tom");
        Student st5 = new Student(5, "Jerry");
        Student st7 = new Student(7, "Lily");

        for (int i = 0; i < webpages.size(); i++) {

            if (i % st3.getId() == 0)
                st3.visit(webpages.get(i));
            if (i % st5.getId() == 0)
                st5.visit(webpages.get(i));
            if (i % st7.getId() == 0)
                st7.visit(webpages.get(i));
        }
        webpages.clear();
        System.gc();
    }
}

class Student {

    private int id;
    private String name;
    private List<WebPage> history = new ArrayList<>();

    public Student(int id, String name) {

        super();
        this.id = id;
        this.name = name;
    }

    public int getId() {

        return id;
    }

    public void setId(int id) {

        this.id = id;
    }

    public String getName() {

        return name;
    }

    public void setName(String name) {

        this.name = name;
    }

    public List<WebPage> getHistory() {

        return history;
    }

    public void setHistory(List<WebPage> history) {

        this.history = history;
    }

    public void visit(WebPage wp) {

        if (wp != null) {

            history.add(wp);
        }
    }
}
class WebPage {

    private String url;
    private String content;

    public String getUrl() {

        return url;
    }

    public void setUrl(String url) {

        this.url = url;
    }

    public String getContent() {

        return content;
    }

    public void setContent(String content) {

        this.content = content;
    }
}

打开生成dump文件:查看线程信息:

浅堆是24是因为student包括3个字段:int4个字节、name4个字节、list是4个字节、对象头8个字节,最后补齐24个字节
&nbsp;

具体的student内容:
15个webpage
每个对应152个字节 15*152=2280字节 --> 即为elementData的实际大小

为什么深堆回收的时候只能回收1288呢?
因为比如0、21、42、35、63、70、84既能被7整除,也能被3或者5整除,所以这7个网页在回收的时候是不能被回收的,所以只能回收8*152=1216,跟1288还是差了72;

那么这72个字节是什么?
15个elementData的元素,每个元素4个字节,即60个字节+对象头8个字节+数组本身4个字节 = 72个字节。
&nbsp;

4.3.6 支配树

&nbsp;
&nbsp;
&nbsp;

4.4 支持使用OQL语言查询对象信息

MAT支持一种类似于SQL的查询语言OQL(Object Query Language)。OQL使用类SQL语法,可以在堆中进行对象的查找和筛选。

4.4.1 select语句

&nbsp;
&nbsp;

4.4.2 from子句

&nbsp;
&nbsp;
&nbsp;

找地址对应的结构,如果直接用student查询的话,可能会查到多个加载器加载的stduent,如果用地址的话查到的就是唯一的我们想要的student

4.4.3 where子句

&nbsp;
&nbsp;

5. Jprofiler

可查看官方文档,做的很好。地址:Jprofiler官方文档

5.1 概述

5.1.1 特点

&nbsp;

5.1.2 主要功能

1、 方法调用:对方法调用的分析可以帮助您了解应用程序正在做什么,并找到提高其性能的方法;
2、 内存分配:通过分析堆上对象、引用链和垃圾收集能帮您修复内存泄露问题,优化内存使用;
3、 线程和锁:JProfiler提供多种针对线程和锁的分析视图助您发现多线程问题;
4、 高级子系统:许多性能问题都发生在更高的语义级别上例如,对于JDBC调用,您可能希望找出执行最慢的SQL语句JProfiler支持对这些子系统进行集成分析;

5.2 下载安装

收费、破解版自己下载,此处略。

5.2.1 Jprofiler集成IDEA

1、 关闭IDEA;
2、 session(会话)->IDEIntegration(IDE集成);
3、 选择自己的idea版本,点击integrate;
&nbsp;
4、 选择自己的idea的配置文件;
&nbsp;
5、 完成;

5.2.2 IDEA集成Jprofiler

1、 直接在idea的plugins中搜索Jprofiler安装;
2、 tools中设置;
&nbsp;

5.2.3 执行程序

选中需要进行监控的程序,检点小蓝点执行:
&nbsp;
Jprofiler中就能看到监控信息:
&nbsp;

5.3 数据采集方式

&nbsp;
内存泄漏用Sampling方式就ok了。
&nbsp;

5.4 具体使用

&nbsp;

5.4.1 遥测:

&nbsp;

&nbsp;

5.4.2 实时内存Live Memory

&nbsp;
&nbsp;
&nbsp;

注意:其中记录对象最好在内存泄漏时使用,因为资源占用较高。

&nbsp;
注意上面的SIZE是浅堆。

1、 markcurrent:内存前后对比:;
&nbsp;
2、 根据记录对象判断是否有内存泄漏的情况:比如如果在垃圾回收后,剩余的存活对象在稳步上升,说明可能存在内存泄漏的情况;
&nbsp;
&nbsp;
&nbsp;

5.4.3 堆遍历Heap walker

&nbsp;

点击Picture。
查找谁使用Picture或者Picture使用了谁:
&nbsp;
查看结果,并根据结果去看对应的图表:
&nbsp;

以下是图表的展示情况:
&nbsp;

5.4.4 cpu视图 cpu views

&nbsp;
访问树:方法访问时间越长,说明cpu占用越多。(其中百分比表示下面被上面被调用的可能性,ms表示时间,inv表示调用次数)。
&nbsp;

方法统计:
&nbsp;

5.4.5 线程视图 Threads

&nbsp;

程序线程监控:
&nbsp;
创建线程dump:
&nbsp;

5.4.6 监视器和锁 Monitors&locks

&nbsp;

5.5 案例分析:

5.5.1 案例1:
/**
 * 功能演示测试

 * @create 12:19
 */
public class JProfilerTest {

    public static void main(String[] args) {

        while (true){

            ArrayList list = new ArrayList();
            for (int i = 0; i < 500; i++) {

                Data data = new Data();
                list.add(data);
            }
            try {

                TimeUnit.MILLISECONDS.sleep(500);
            } catch (InterruptedException e) {

                e.printStackTrace();
            }
        }
    }
}
class Data{

    private int size = 10;
    private byte[] buffer = new byte[1024 * 1024];//1mb
    private String info = "hello,atguigu";
}

&nbsp;

5.5.2 案例2:
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";
    // 注意是类变量
    static ArrayList list = new ArrayList();
}

进入jprofiler,可以看到内存和类在不断变大,同时GC是正常执行的:
&nbsp;
&nbsp;

&nbsp;
点击byte数组,分析heap walker:
&nbsp;
查看都被谁引用了:
&nbsp;
查看具体的引用关系:
&nbsp;
可以看出byte[]来自于Bean类是的list中,并且这个list是ArrayList类型的静态集合,所以找到了:static ArrayList list = new ArrayList();发现list是静态的,这不妥,因为我们的目的是while结束之后Bean对象被回收,并且Bena对象中的所有字段都被回收,但是list是静态的,那就是类的,众所周知,类变量随类而生,随类而灭,因此每次我们往list中添加值,都是往同一个list中添加值,这会造成list不断增大,并且不能回收,所以最终会导致OOM。

6. Arthas

建议本章节全部查看官方文档,不要看我写的。。。arthas官方文档

6.1 概述

6.1.1 jvisualvm和jprofiler的优缺点:

&nbsp;

6.1.2 概述:

&nbsp;

6.1.3 基于那些工具组合而成:

&nbsp;

6.2 安装和使用:

见官方文档:arthas官方文档

6.2.1 安装:

&nbsp;
&nbsp;

6.2.2 卸载:

&nbsp;

6.2.3 工程目录:

&nbsp;

6.2.4 启动

&nbsp;

6.2.5 一些操作:
# 查看进程:
jps
#查看日志
cat ~/logs/arthas/arthas.log
#查看帮助
java -jar arthas-boot.jar -h
# 退出:
quit\exit
# 关闭:
stop\shutdown
6.2.6 web console

&nbsp;

6.3 相关诊断指令

可以看官方文档,建议直接查看官方文档。

6.3.1 基础指令:

&nbsp;

1、 根据jps出的java进程,启动arthas监控某个进程:;
&nbsp;
2、 help:查看命令帮助,以及查看某个命令具体怎么使用:;
&nbsp;

6.3.2 jvm相关:

&nbsp;

1、 dashboard:可以看到重复的数据有很多个,是因为每隔一段时间就会打印一次,可以通过dashboard-i500设置间隔500毫秒打印一次,同时可以设置dashboard-n4设置打印4次,可以dashboard-i1000-n4一起配置;
&nbsp;
2、 thread:查看线程线程相关,可以threadid查看具体线程信息,thread-b查看死锁的情况,同样的也有thread-i5000查看5秒内cpu利用率,thread-n2查看cpu使用率前2的线程;
&nbsp;

6.3.3 class/classloader相关:

&nbsp;

1、 sc:;
&nbsp;
&nbsp;
2、 sm:;
&nbsp;
&nbsp;
3、 jad:将class文件反编译成java文件;
&nbsp;
4、 mc和redefine配合使用:;
&nbsp;
&nbsp;
5、 ClassLoader;
&nbsp;
&nbsp;

7.3.4 monitor/watch/trace相关:

&nbsp;

1、 monitor:;
&nbsp;
&nbsp;
2、 watch;
&nbsp;
&nbsp;
3、 trace;
&nbsp;
&nbsp;
4、 stack:;
&nbsp;

7. Java Mission Control

jdk自带的分析工具:我看了下jdk8里面有,jdk11以后的版本没有了。
&nbsp;
想要学习的可以自己看视频,这里不多说,视频子啊P359~P360

8. 其他工具

1、 FlameGraphs火焰图;
&nbsp;
&nbsp;
2、 Tprofiler;
&nbsp;
3、 Btrace;
4、 Yourkit;
5、 Hprobe;
6、 SpringInsight;