JVM监控诊断之工具使用(下篇)

Posted ProChick

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JVM监控诊断之工具使用(下篇)相关的知识,希望对你有一定的参考价值。

一、Eclipse MAT工具

💡:Memory Analyzer Tool,一款基于Eclipse的内存分析工具( 插件形式 ),是一个快速、高效、功能丰富的Java堆内存分析工具,可以帮助我们查找内存泄露的原因,以便减少内存消耗

1.基本使用

软件下载:https://www.eclipse.org/mat/downloads.php

插件下载

2.主要功能

MAT工具主要用于分析堆存储Dump文件,可以获取的信息如下:

  • 所有的对象信息,包括:对象实例、成员变量、存储于栈中的基本数据类型值和其它对象的引用值。
  • 所有的类信息,包括:类加载、类名称、父类、静态变量等。
  • 所有的线程信息,包括:线程的调用栈、线程的局部变量。
  • GCRoot到所有对象的引用路径。

获取堆Dump文件

分析堆Dump文件

  • 概览信息

  • 堆Dump文件信息

  • 类实例占用情况直方图

    对象的浅堆大小

    浅堆(Shallow Heap)是指一个对象所消耗的内存,以String对象为例,在String中包含两个int类型和一个char型数组的引用。因为在Java中一个int类型占用4个字节,一个引用类型占用4个字节,对象的头信息占用8个字节,所以共占用20个字节,但又由于对象的大小需要是8的倍数,故需要24个字节。那么这24个字节即为String对象的浅堆大小,它与String的取值无关。

    对象的深堆大小

    深堆(Retained Heap)是指对象的保留集中所有对象的浅堆大小之和,也就是对象被回收后,可以释放的真正空间大小。这里的保留集是指当对象被回收时,可以被释放的所有对象集合以及对象本身,其中对象集合是那些只能通过当前对象直接或者间接访问的对象,换句话来说就是那些仅仅被当前对象所持有的对象。

    对象的实际大小

    对象的实际大小是指一个对象所能触及的所有对象的浅堆大小之和,也就是通常我们所说的对象大小。

    💡举例

    👉A对象的浅堆大小:A本身,A对象的深堆大小:A和D,A对象的实际大小:A和C和D

    👉B对象的浅堆大小:B本身,B对象的深堆大小:B和E,B对象的实际大小:B和C和E

  • 线程信息概览

    查看系统中的Java线程以及线程中局部变量信息

    针对某一个变量查看出引用和入引用信息,也就是引用了谁和被谁引用。

  • 对象的支配树信息

    什么是支配树?

    MAT提供了一个称为支配树的对象引用图,它体现了对象实例之间的支配关系。在对象引用图中,如果所有指向对象B的路径都要经过对象A,则认为对象A支配对象B。另外在所有对象B的支配者中,如果对象A是离对象B最近的支配者,则认为对象A是对象B的直接支配者。

    基本性质

    • 所有被对象A支配的对象集合称之为对象A的保留集,也就是所谓的深堆
    • 如果对象A支配对象B,那么对象A的直接支配者也支配对象B

    举例(左边是对象引用图,右边是对象支配树)

3.注意事项

  • MAT工具能够为开发人员快速生成一个Java程序的内存泄露报表,但很多内存问题还是需要我们自己通过经验和直觉来判断发现。
  • MAT不是一个万能的工具,它并不能够处理分析所有类型的堆转储文件。

4.内存泄漏(扩展)

  • 概述

    • 可达性分析算法是用来判断一个对象是否还在被引用,当一个对象不再被使用,但是还在被引用着的时候,这就造成JVM无法对其进行回收,所以造成了内存泄漏。
    • 严格意义上来讲,只有对象不被使用但又不能够被回收的对象,才称之为内存泄漏。
    • 宽泛意义上来讲,当一个对象的生命周期因某些原因变得很长,也可称为内存泄露。
  • 分类

    • 经常发生:发生内存泄漏的代码经常被执行
    • 偶然发生:发生内存泄漏的代码偶尔被执行
    • 一次发生:发生内存泄漏的代码只执行一次
    • 隐式发生:一直站着内存不释放,直到程序运行结束
  • 情况

    • 静态集合

      public class Test()
          public static List<Object> list = new ArrayList<>();
          
          public void add()
              Object obj = new Object();
              list.add(obj);
          
      
      

      如果类中包含有静态的集合容器,那么它们的生命周期就会和JVM进程一致,这就导致了容器中的对象在程序结束之前,始终不能够被回收,最终导致OOM。

    • 单例模式

      在单例模式中,创建的对象生命周期也是和JVM进程一致的,所以如果单例对象持有外部对象的引用,则被引用的对象在程序结束之前,始终不能够被回收,最终导致OOM。

    • 内部类持有外部类

      如果一个外部类的实例对象中的某个方法返回了一个内部类的实例对象,并且这个内部类的实例对象又被其它类对象长期引用,那么这个外部类对象就无法被回收,最终导致OOM。

    • 各种资源连接

      在进行数据库连接、网络连接或者是IO流连接的场景下,当我们不再使用时,如果我们不调用close方法进行关闭的话,垃圾回收器就永远不会回收这些对象,最终导致OOM。

    • 变量不合理的作用域

      public class Test()
          private String msg;
          
          public void add()
              msg = readFormFile(); // 从文件读取内容
              save(msg);            // 保存到数据库
          
      
      

      如果一个变量定义的作用范围大于了它的使用范围,就有可能造成内存泄漏。比如上面的代码中,msg变量的作用范围仅仅在add方法内,所以应该将变量定义在方法内部。

    • 改变哈希值

      当一个对象存储到HashSet集合中的时候,就不能修改这个对象中那些参与哈希值计算的字段了。否则,对象修改后的哈希值与原来的哈希值就不同了,进而我们就不能从集合中删除该对象,这就造成了内存泄漏。这也是为什么把String类型设置为不可变类型的原因,不过如果我们想把自定义的类对象保存在Hash集合中,就必须保证对象的hash值不可变。

    • 缓存泄漏

      内存泄漏的另外一种常见来源就是缓存,因为缓存中的数据很容易被我们遗忘,然后就一直保留在程序运行期得不到清理。在实际开发中,我们可以使用WeakHashMap来进行数据缓存。

    • 监听器和回调

      如果客户端在服务端的API中进行了注册回调,但是没有显式的进行取消,那么就会造成对象的积累,最终导致OOM。

5.OQL查询语言(扩展)

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

  • Select子句

    # 查询对象的所有引用实例
    select * from java.util.Vector v
    
    # 将查询的结果集以对象的形式显示
    select objects v.elementData from java.util.Vector v
    
    # 查询对象的保留集
    select as retained set * from java.util.Vector
    
    # 将查询的结果集进行去重
    select distinct objects classof(v) from java.util.Vector v
    
  • From子句

    # 指定类名查询
    select * from java.lang.String
    
    # 正则查询
    select * from "java\\lang\\..*"
    
  • Where子句

    # 查询长度大于10的数组
    select * from char[] c where c.@length > 10
    
    # 查询不为空的字符串
    select * from java.lang.String s where s.value != null
    
    # 模糊查询
    select * from java.lang.String s where toString(s) like ".*java.*"
    
  • 内置对象属性

    # 查看结果集中每个数组的长度
    select v.elementData.@length from java.util.Vector v
    

二、JProfiler工具

💡:是一款商业软件,功能更加强大

1.基本概述

  • JProfiler是由ej-technologies公司开发的一款Java应用程序性能诊断工具,当我们想要在程序运行期间查看内存的占用情况的话,就可以使用JProfiler工具。
  • JProfiler工具使用方便、操作简单、对被分析的应用影响小、内存分析强大。
  • JProfiler工具支持对JDBC、NoSQL、Servlet、Socket等进行分析。
  • JProfiler工具支持在线分析和离线分析,支持本地分析和远程分析。
  • JProfiler工具具有跨平台性。

2.主要功能

  • 方法调用

    对方法调用的分析可以帮助我们了解应用程序的执行过程,然后找到提高性能的方法。

  • 内存分配

    通过分析堆空间中的对象、引用链和垃圾收集器能够帮助我们发现内存泄漏的问题。

  • 线程和锁

    提供了丰富的线程和锁的分析视图。

  • 高级子系统

    比如对于JDBC子系统的集成分析,帮助我们找到系统中执行比较慢的SQL语句。

3.安装配置

下载地址:https://www.ej-technologies.com/download/jprofiler/verion_100

  • 在JProfiler端集成IDEA

  • 在IDEA中集成JProfiler

  • 启动

4.基本使用

  • 数据采集

    首先打开一个正在运行的Java应用程序进程

    • 重构模式(Instrumentation)

      这是JProfiler的全功能模式,在类被加载之前,JProfiler会把相关监控功能的代码写入到类的字节码文件中,然后确保监控堆栈信息的准确性。然而如果我们需要分析的类比较多,那么就会引起很大的CPU开销,对系统的性能有一定的影响。不过我们可以结合过滤器使用,也就是只对特定的类进行分析。

    • 抽样模式(Sampling)

      类似于样本统计,也就是每隔一段时间进行一次堆栈信息的统计,这种方式对CPU的开销比较低,对系统的性能造成的影响比较小。所以如果我们要对正在运行中的程序进行监控,然后查找一些内存溢出或者泄漏问题的话,就推荐使用这种方式。

  • 遥感监测

    • 监控概览Overview

    • 内存监控Memory

    • 垃圾回收监控GC Activity

    • 类加载监控Classes

    • 线程监控Threads

    • CPU监控CPU Load

  • 内存监控

    • 所有的对象 All Objects:显示所有加载的类列表和在堆上分配的实例数。
    • 记录的对象 Record Objects:显示特定时间段的对象分配,并记录分配的调用堆栈。
    • 分配访问树 Allocation Call Tree:显示一颗请求树或者方法、类、包。
    • 分配的热点 Allocation Hot Spots:显示一个方法、类或者包的列表。
    • 类的追踪器 Class Tracker:显示选定类和包的实例与时间。
  • 堆栈监控

    • 导出堆转储快照

    • 分析堆转储快照

  • CPU监控

  • 线程分析

  • 线程监控

    • 线程历史 Thread History:显示线程活动和线程状态相关联的活动时间表
    • 线程监控 Thread Monitor:显示所有活动线程的状态
    • 线程转储 Thread Dumps:显示所有线程的堆栈跟踪,并生成线程快照文件
  • 监视器和锁

    • 死锁探测图表 Current Locking Graph:显示JVM中的死锁图表
    • 正在使用的检测器 Current Monitors:显示正在使用的检测器和相关联的线程信息
    • 锁定历史图表 Locking History Graph:显示JVM中的锁定历史
    • 历史检测记录 Monitor History:显示等待事件和阻塞事件的历史记录
    • 监视器使用统计 Monitor Usage Statistics:显示线程和检测类的统计数据

三、Arthas工具

1.基本概述

  • Arthas是Alibaba开源的Java诊断工具,能够实现在线排查问题并且无需重启,能够动态的跟踪Java代码,能够实时的监控JVM的状态。
  • Arthas支持Linux、Mac、Windows等主流操作系统,采用命令行交互模式,方便进行问题的定位和诊断。

2.解决哪些问题

  • 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?
  • 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?
  • 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?
  • 线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!
  • 是否有一个全局视角来查看系统的运行状况?
  • 有什么办法可以监控到JVM的实时运行状态?
  • 怎么快速定位应用的热点,生成火焰图?
  • 怎样直接从JVM内查找某个类的实例?

3.安装使用

下载地址:https://alibaba.github.io/arthas/arthas-boot.jar

下载地址:https://arthas.gitee.io/arthas-boot.jar

  • 源码工程目录

  • 启动并监控

    方式一

    java -jar arthas-boot.jar
    

    选择需要监控的进程

    方式二

    # 直接指定需要监控的进程ID
    java -jar arthas-boot.jar 5826
    

  • 使用

    命令行方式

    web界面方式:访问127.0.0.1:8563

  • 关闭

      # 关闭当前客户端
      使用quit或者exit命令
      
      # 关闭所有相关服务
      使用stop或者shutdown命令
    

4.基本指令

  • help:查看命令的帮助信息
  • cat:打印文件内容
  • echo:打印参数
  • grep:匹配查找
  • tee:复制标准输入到标准输出
  • pwd:打印当前工作目录
  • cls:清空当前屏幕
  • session:查看当前会话
  • reset:重置Arthas中的增强类
  • version:显示Arthas的版本号
  • history:显示使用过的命令
  • keymap:查看Arthas的快捷键列表
  • quit:关闭当前Arthas客户端
  • stop:关闭Arthas服务端

5.JVM相关指令

  • dashboard:显示当前系统监控的数据面板

    # 每隔指定时间段打印一次(单位为毫秒)
    dashboard -i 5000
    
    # 一共打印n次
    dashboard -n 5
    
  • thread:显示JVM中线程堆栈的信息

    # 打印当前程序所有线程信息
    thread
    
    # 打印指定ID线程的详细信息
    thread [ID]
    
    # 打印当前程序的阻塞线程
    thread -b
    
    # 在指定时间段内统计一次每个线程的CPU利用率(单位为毫秒)
    thread -i 5000
    
    # 打印所有线程中占用CPU较高的前n个线程
    thread -n 5
    
  • jvm:显示当前JVM的详细信息

  • sysprop:显示和修改JVM的系统属性

  • sysenv:显示JVM的环境变量

  • vmoption:显示和修改JVM中有关诊断的option

  • perfcounter:显示JVM中有关Pref Counter的信息

  • logger:显示和修改logger

  • getstatic:显示类的静态属性

  • ognl:执行ognl表达式

  • mbean:显示Mbean的信息

  • heapdump:导出堆转储文件

6.类加载相关指令

  • sc:显示JVM已经加载的类信息

  • sm:显示JVM已经加载的类的方法信息

  • jad:对指定已加载类的源码进行反编译

  • mc:调用内存编译器,将源码文件编译为字节码文件

  • retransform:加载外部的字节码文件并retransform到当前JVM中

  • redefine:加载外部的字节码文件并redefine到当前JVM中

  • dump:dump已加载类的字节码文件到指定目录

  • classloader:显示类加载器的继承树、类加载的信息

    # 查看ClassLoader的继承树
    classloader -t
    
    # 按类的加载实例查看统计信息
    classloader -l
    
    # 用classloader对应的hashcode查看该类加载器对应加载的jar包路径
    classloader -c [hashcode]
    

7.方法监控指令

  • monitor:对指定方法进行监控

    # 每隔5秒钟打印一次Test类中默认构造器方法的调用信息
    monitor -c 5 com.example.Test <init>
    
  • watch:对指定方法执行中的数据进行监控

  • trace:输出指定方法内部调用的路径信息

  • stack:输出指定方法被调用的路径信息

  • tt:输出指定方法每次被调用的入参和返回信息

以上是关于JVM监控诊断之工具使用(下篇)的主要内容,如果未能解决你的问题,请参考以下文章

JVM监控诊断之工具使用(下篇)

JVM监控诊断之工具使用(下篇)

JVM监控诊断之工具使用(上篇)

JVM监控诊断之工具使用(上篇)

JVM监控诊断之工具使用(上篇)

JVM监控诊断之工具使用(上篇)