当前位置: 首页 > article >正文

JVM之工具篇

简介

这里总结了分析jvm问题时需要用到的工具

工具相关的知识点

为了避免知识点记录的太过分散,把它们集中记录在这里,这些都是使用jvm工具时需要知道的

堆转储文件

Heap Dump,是JVM运行时堆内存的快照,它记录了当前JVM中所有对象的状态和引用关系。堆转储文件通常用于分析内存泄漏、内存溢出等问题。堆转储文件只可以查看内存中有哪些对象,它无法展示这些对象是在代码中的什么位置生成的,这需要用户自己分析。

orcale系列的jvm生成的堆转储文件是hprof格式的。

分析堆转储文件,至少需要文件的1.5倍到2倍内存。

QQL

Object Query Language,对象查询语言,用于分析堆转储文件。可以帮助用户快速定位特定对象、查找大对象等

语法:

SELECT <expression>
FROM <class> <alias>
WHERE <condition>

案例:

  • 查找String类的所有实例:SELECT s FROM java.lang.String s

压缩类空间

这个空间是元数据空间的一部分,本质上也是存储类的元数据,它和指针压缩有关。在64位的JVM上,如果堆内存小于32G,会默认开启指针压缩,把8字节的指针压缩为4字节。压缩类空间存储压缩后的类指针

jstat命令统计gc信息时,会统计到这个空间的内存。

java自带的工具

jps

列出目标系统上正在运行的jvm,包括进程ID和main方法所在类的名称

语法:jps [选项] [进程ID]

  • 选项:
    • -m:显示传递给main方法的实参
    • -l:显示应用main方法所在类的包名或jar文件的路径名
    • -v:显示传递给jvm的实参

案例:

执行命令:jps -l -m

返回结果:

35845 sun.tools.jps.Jps -l -m
35643 com.intellij.idea.Main

查看虚拟机运行情况的工具

jinfo 查看虚拟机参数

用于查看和动态修改虚拟机运行时配置的参数。

案例:jinfo -flags <pid>,查看虚拟机的运行时参数

jstat 查看虚拟机的统计信息

查看虚拟机的统计信息,这个命令是实验性的,并且必须要配合选项一起使用

语法:jstat 选项 pid [interval] [count],选项是必选的,通过 jstat -options 查看有哪些可选的option

使用案例
案例1 查看选项

执行命令:jstat -options

结果:

-class
-compiler
-gc
-gccapacity
-gccause
-gcmetacapacity
-gcnew
-gcnewcapacity
-gcold
-gcoldcapacity
-gcutil
-printcompilation

可以看到,可以统计类信息、gc信息、编译信息等

案例2 查看类信息

执行命令:jstat -class ${pid}

结果:

Loaded  Bytes  Unloaded  Bytes     Time   
   534  1083.1        0     0.0       0.04

结果解析:-class选项可以统计JVM的类加载和卸载情况。

结果中的字段:

  • Loaded:被加载的类的数量
  • Bytes:被加载的类的字节数量
  • Unloaded:被卸载的类的数量
  • Bytes:被卸载的类的字节数量
  • Time:执行类的加载和卸载锁花费的时间
案例3 查看gc信息

执行命令:jstat -gc ${pid}

结果:

在这里插入图片描述

结果解析:统计gc相关的信息

结果中的字段:

  • S0C、S1C:幸存者空间的容量,单位是kb,就是分代垃圾回收中的from servivor、to servivor
  • S0U、S1U:幸存者空间的使用量
  • EC、EU:Eden区的容量、使用量
  • OC、OC:老年代的容量、使用量
  • MC、MU:元空间的容量、使用量
  • CCSC 和 CCSU:压缩类空间的容量、使用量
  • YGC 和 YGCT:年轻代垃圾回收次数为 0,总耗时为 0 秒。
  • FGC 和 FGCT:老年代垃圾回收次数为 0,总耗时为 0 秒。
  • GCT:垃圾回收的总耗时为 0 秒。
案例4 查看gc信息 每隔3秒在屏幕上打印1次,1分钟后退出

执行命令:jstat -gc ${pid} 3s 20,每个3s打印一次gc信息,打印20次后退出

jstack 查看虚拟机的线程信息

查看虚拟机的线程信息

jstack 的主要功能:

  • 生成线程快照:打印出指定 Java 进程中所有线程的堆栈信息,包括线程的名称、状态、调用栈和锁信息。
  • 诊断死锁:通过分析线程的堆栈信息,可以发现线程间的死锁情况,因为死锁总是发生在两个或多个线程之间

语法:jstack [-l | -m | -F] pid

  • 选项
    • -l:长列表,打印关于锁的信息
    • -m:打印java 和 c++的线程信息
使用案例
案例1 查看线程的执行信息

执行命令:jstack ${pid}

输出结果:
在这里插入图片描述

打印内容讲解:

"Attach Listener" #11 daemon prio=9 os_prio=31 tid=0x0000000123808800 nid=0x4207 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

以上面这段内容为例:

  • “Attach Listener”:线程名称
  • #11:表示这是 JVM 中的第 11 个线程
  • daemon:守护线程
  • prio=9 os_prio=31:优先级和操作系统优先级
  • tid:jvm级别的线程id
  • nid:操作系统级别的线程id
  • java.lang.Thread.State: RUNNABLE:RUNNABLE表示线程正在运行或准备好运行

jmap 查看堆内存

用于生成 Java 堆转储文件和查看JVM的内存使用情况。它通常用于诊断内存泄漏、内存溢出等问题

语法:jmap [options] pid,默认打印共享对象内存信息

  • 选项:
    • -heap:打印堆内存信息
使用案例
案例1 查看堆内存统计信息

执行命令:jmap -heap ${pid}

结果:

1、堆内存统计信息:
在这里插入图片描述

堆配置参数

  • MinHeapFreeRatio = 40:堆内存的最小空闲比例。当堆内存的空闲比例低于此值时,JVM 会尝试扩展堆内存。
  • MaxHeapFreeRatio = 70:堆内存的最大空闲比例。当堆内存的空闲比例高于此值时,JVM 会尝试收缩堆内存。
  • MaxHeapSize = 383778816 (366.0MB):堆的最大大小(-Xmx 参数)。
  • NewSize = 8388608 (8.0MB):新生代的初始大小(-Xmn 参数的一部分)。
  • MaxNewSize = 127926272 (122.0MB):新生代的最大大小。
  • OldSize = 16777216 (16.0MB):老年代的初始大小。
  • NewRatio = 2:新生代与老年代的比例(新生代:老年代 = 1:2)。
  • SurvivorRatio = 8:Eden 区与 Survivor 区的比例(Eden : Survivor = 8:1)。
  • MetaspaceSize = 21807104 (20.796875MB):元空间的初始大小。
  • MaxMetaspaceSize = 1759218604032 (1610.0GB):元空间的最大大小(默认值通常很大)。
  • G1HeapRegionSize = 0 (0.0MB):G1 垃圾回收器的区域大小。

2、堆内存使用情况

在这里插入图片描述

tenured generation是老年代,interned String是字符串常量池中的字符串数量

案例2 查看对象的统计信息

执行命令:jmap -histo <pid>

在这里插入图片描述

输出内容讲解:

  • class name字段中的值:[C 表示字符数组、[B 表示字节数组、[I 表示int数组,数组前面会有前缀 [,这是字节码指令中的知识点

这个命令可以帮助用户快速定位实例数量过多或占用内存过大的类

案例3 生成堆转储文件

执行命令:jmap -dump:format=b,file=文件名.hprof ${pid},文件会生成到当前目录下,这是一个二进制文件,存储了堆的快照信息,需要使用专业工具查看

jcmd 向jvm发送命令

一个命令行工具,用于向正在运行的Java进程发送诊断命令。它提供了多种功能,包括生成堆转储、查看线程信息、触发垃圾回收等

使用案例
案例1 查看帮助命令

执行命令:jcmd <pid> help,它可以获取指定jvm实例支持的命令

案例2 生成堆转储文件

执行命令:jcmd <pid> GC.heap_dump 文件名,会生成到程序目录下

堆转储文件是jvm的快照文件,存储了jvm在某一刻的执行信息。

案例3 查看类加载器的统计信息

执行命令: jcmd <pid> VM.classloader_stats

结果:

在这里插入图片描述

统计了每个类加载器加载了几个类,这些类占用的内存大小

案例4 查看线程的执行信息

执行命令:jcmd <pid> Thread.print,这个命令类似于jstack命令

jconsole 图形化监控工具

图形化界面,查看java程序的信息,包括内存使用率、CPU使用率、线程数、类加载情况等,还可以手动执行垃圾回收、检测死锁等。

使用方式:在命令行直接输入 jconsole 命令即可使用,它会自动检查当前机器上运行的jvm

启动界面:

在这里插入图片描述

监控界面:

在这里插入图片描述

使用案例
案例1 执行GC

在这里插入图片描述

案例2 检测死锁

在这里插入图片描述

如何连接到远程服务器上的进程?

需要进程本身支持远程连接,在进程启动时添加参数,例如:

java -jar -Dcom.sun.management.jmxremote.port=8899 \
     -Dcom.sun.management.jmxremote.authenticate=false \
     -Dcom.sun.management.jmxremote.ssl=false \
     -Djava.rmi.server.hostname=192.168.0.3 \
     math-game.jar

在进程启动时,添加jmx远程支持,指定进程本身的ip地址和端口号,然后就可以使用jconsole远程连接了

在这里插入图片描述

jvisualvm 图形化监控工具

图形化界面,查看java程序的信息,包括内存使用率、CPU使用率、线程数、类加载情况等,还可以手动执行垃圾回收、打印堆快照等。

这个命令是Java自带的,但是Java8在某些版本之后不支持这个软件了,需要自行下载。

下载地址:https://visualvm.github.io/download.html

使用方式:在命令行直接输入 jvisualvm 命令即可使用,它会自动检查当前机器上运行的jvm。

使用案例
案例1 查看堆转储文件

1、将堆转储文件加载到visualVM中。(这里mac上的图标,其它平台的可能不一样)

在这里插入图片描述

2、查看程序的统计信息

在这里插入图片描述

3、查看大对象:某些对象可以右击选择“select in threads”,查看它被哪个线程持有。在这个案例中,大对象是存储在ArrayList中的字节数组,可以在代码中搜索一下,找到这样的数组,然后再继续分析。

在这里插入图片描述

4、查看线程的执行信息,可以直接看到抛出内存溢出的位置,但是这未必是需要修复的位置,还是需要分析内存中的大对象是怎么来的,然后优化这部分对象。

在这里插入图片描述

5、QQL,一种查询语言,类似于SQL,可以查找某个类的所有实例。

在这里插入图片描述

第三方工具

arthus

arthas,阿里巴巴开源的Java诊断工具,用于诊断线上问题。

安装

下载arthas的jar包即可,curl -O https://arthas.aliyun.com/arthas-boot.jar

第一次启动arthas时,会下载arthas需要的库文件到用户根目录的 “.arthas” 文件中,arthas的日志文件在用户根目录的 “logs/arthas” 中

快速入门

通过一个实际案例来演示arthas的使用。

第一步:准备

  • 下载arthus:curl -O https://arthas.aliyun.com/arthas-boot.jar
  • 下载arthus提供的用于演示的jar包:curl -O https://arthas.aliyun.com/math-game.jar

第二步:启动arthas

  • 启动演示jar包:java -jar math-game.jar,这个jar包中提供了一个小程序,用于计算某个随机数的可以被分解成哪些因数
  • 启动arthus:java -jar arthas-boot.jar

第三步:使用arthas来查看正在运行的Java项目

  • 启动之后,进入arthas的交互界面,它会列出当前机器上运行的Java程序,用户选择一个进入,arthas会attach到该进程
  • attach到目标进程后,可以执行的命令:
    • daashboard:查看当前进程的详细信息

arthas命令

输入help命令,可以查看arthus提供了哪些命令。这里介绍了几个我用过的命令

jvm相关
监控面板 dashboard

当前系统的实时面板,默认5s刷新一次,按q退出。

面板中的数据:包括线程信息、内存信息、jvm信息

线程部分:

  • ID:Java 级别的线程 ID
  • NAME: 线程名
  • GROUP: 线程组名
  • PRIORITY: 线程优先级, 1到10之间的数字,越大表示优先级越高
  • STATE: 线程的状态
  • CPU%: 线程的cpu使用率。比如采样间隔1000ms,某个线程的增量cpu时间为100ms,则 cpu 使用率 =100/1000 = 10%
  • DELTA_TIME: 上次采样之后线程运行增量CPU时间,数据格式为秒
  • TIME: 线程运行总 CPU 时间,数据格式为分:秒
  • INTERRUPTED: 线程当前的中断位状态
  • DAEMON: 是否是daemon线程
堆转储文件 heapdump

类似 jmap 命令的 heap dump 功能

案例:heapdump arthas-output/dump.hprof

查看jvm信息 jvm

包括系统信息、类加载器信息、内存信息、线程信息的基本统计

查看内存信息 memory

堆内存中各个部分的使用信息

采样 生成火焰图 profiler

用于生成应用热点的火焰图。它通过不断采样,将收集到的调用链路生成火焰图,帮助开发者快速定位性能瓶颈

案例1:profiler start -e cpu -d 120,采样CPU使用情况,持续120秒,会生成一个html文件

案例2:

  • 开始采样:profiler start -e cpu,采样事件为cpu
  • 查看采样状态:profiler status
  • 停止采样并生成结果:profiler stop,会生成一个html文件

火焰图应该如何分析:火焰图的Y轴表示调用栈,每一层都是一个函数;X轴表示采样数,宽度越宽表示该函数被采样的次数越多,即占用的CPU时间越长。在火焰图中,热点方法通常位于图的顶部,且宽度较宽。这些方法表示在采样期间占用CPU时间最多的函数

类加载器相关
类加载器 classloader

查看当前类加载器的信息

选项:

  • -c:哈希值:指定当前classloader的哈希值,可以用于查看具体的classloader的信息
  • -a:列出当前classloader加载的所有类
  • -t:查看类加载器的继承树
  • -l:列出更多信息
案例1:执行classloader命令

输出结果:

 name                                       numberOfInstances  loadedCountTotal
 BootstrapClassLoader                       1                  2358
 com.taobao.arthas.agent.ArthasClassloader  1                  1574
 sun.misc.Launcher$ExtClassLoader           1                  47
 sun.reflect.DelegatingClassLoader          16                 16
 sun.misc.Launcher$AppClassLoader           1                  4
Affect(row-cnt:5) cost in 6 ms.

结果中,numberOfInstances是类加载器的实例数量,loadedCountTotal是类加载器加载了多少个类

案例2:查看累加器的继承树

执行命令:classloader -t

输出结果:

+-BootstrapClassLoader
+-sun.misc.Launcher$ExtClassLoader@74a14482
  +-com.taobao.arthas.agent.ArthasClassloader@4bead177
  +-sun.misc.Launcher$AppClassLoader@55f96302
Affect(row-cnt:4) cost in 7 ms.
案例3:类加载器的哈希值

执行命令:classloader -l

输出结果:

 name                                                loadedCount  hash      parent
 BootstrapClassLoader                                2361         null      null
 com.taobao.arthas.agent.ArthasClassloader@4bead177  1580         4bead177  sun.misc.Launcher$ExtC
                                                                            lassLoader@74a14482
 sun.misc.Launcher$AppClassLoader@55f96302           4            55f96302  sun.misc.Launcher$ExtC
                                                                            lassLoader@74a14482
 sun.misc.Launcher$ExtClassLoader@74a14482           47           74a14482  null
案例4:查看classloader的加载路径

通过-c选项指定类加载器的哈希值

执行命令: classloader -c 74a14482,这里查看的是扩展类加载器的加载路径

输出结果:

file:/D:/java/jre/lib/ext/access-bridge-64.jar
file:/D:/java/jre/lib/ext/cldrdata.jar
file:/D:/java/jre/lib/ext/dnsns.jar
file:/D:/java/jre/lib/ext/jaccess.jar
file:/D:/java/jre/lib/ext/jfxrt.jar
file:/D:/java/jre/lib/ext/localedata.jar
file:/D:/java/jre/lib/ext/nashorn.jar
file:/D:/java/jre/lib/ext/sunec.jar
file:/D:/java/jre/lib/ext/sunjce_provider.jar
file:/D:/java/jre/lib/ext/sunmscapi.jar
file:/D:/java/jre/lib/ext/sunpkcs11.jar
file:/D:/java/jre/lib/ext/zipfs.jar
Affect(row-cnt:24) cost in 1 ms.
案例5:列出当前classloader加载的所有类

执行命令:classloader -c 55f96302 -a

返回结果:

hash:55f96302, sun.misc.Launcher$AppClassLoader@55f96302
com.taobao.arthas.agent.ArthasClassloader
com.taobao.arthas.agent334.AgentBootstrap
com.taobao.arthas.agent334.AgentBootstrap$1
demo.MathGame
反编译字节码 jad

反编译类的字节码

案例:jad 类的全限定名

方法相关
方法执行监控 monitor

监控方法的平均执行时间和失败率

格式:monitor [-c 执行周期] 类名 方法名,执行周期默认是120s

案例:monitor -c 5 demo.MathGame print

函数执行数据观测 watch

监控方法的执行时间和返回结果

格式:watch 类的全限定名 方法名

案例:watch demo.MathGame print

MAT

Eclipse Memory Analyzer Tool,JVM堆内存离线分析工具,基于eclipse开发,用于分析堆转储文件。

MAT提供了称为支配树(dominator tree)的对象图,支配树展示了对象实例之间的引用关系。

浅堆和深堆:

  • 浅堆:shallow heap,支配树中对象本身占用的空间称为浅堆
  • 深堆:retained heap,支配树中一个对象和它的子树称为深堆,深堆表示如果对象被回收掉,可以释放多少内存

MAT根据支配树,从叶子节点向根节点遍历,发现如果深堆的大小超过一定阈值比例,就会将其标记成为内存泄漏的嫌疑对象。

对象的引用:

  • outgoing reference:查看对象所引用的对象
  • incoming reference:查看对象被哪些对象引用

MAT提供的功能:

  • Overview:快速查看内存使用情况。
  • Histogram:柱状图,查看对象的分布情况,包括实例数量和内存占用。
  • Dominator Tree:支配树,查找占用最多内存的对象及其引用路径。

操作界面:

在这里插入图片描述

MAT会查看对象间的引用信息,分析可能存在的内存泄漏点,然后给出分析报告

常用操作

查看jvm的运行情况

查看jvm的启动参数

方法一:jps命令,使用不同的选项,-v 显示传递给jvm的实参,-m 显示传递给main方法的实参

方法二:jinfo -flags 进程id

案例:

在这里插入图片描述

参数讲解:

  • -XX:CICompilerCount=2 :设置编译器线程的数量
  • -XX:InitialHeapSize=25165824 :设置 JVM 启动时的初始堆大小, 24M
  • -XX:MaxHeapSize=383778816 :设置 JVM 的最大堆大小
  • -XX:MaxNewSize=127926272 :设置新生代的最大大小
  • -XX:MinHeapDeltaBytes=196608 :设置堆内存扩展的最小增量,当 JVM 需要扩展堆内存时,每次扩展的最小增量。此参数可以避免频繁的内存扩展操作,提高性能。
  • -XX:NewSize=8388608 :设置新生代的初始大小
  • -XX:OldSize=16777216 :设置老年代的初始大小
  • -XX:+UseCompressedClassPointers :启用压缩类指针,在 64 位 JVM 中,启用压缩类指针可以减少内存占用,提高性能。此参数在 Java 8 及更高版本中默认启用
  • -XX:+UseCompressedOops :启用压缩普通对象指针

查看jvm中线程的执行信息

jstack 进程id

查看jvm中堆内存的统计信息

jmap -heap 进程id

查看jvm堆中对象的统计信息

jmap -histo 进程id

查看jvm使用哪个垃圾收集器

Java8默认使用吞吐量优先的垃圾收集器。通过查看jvm的启动参数 jinfo -flags 进程id 来查看,如果没有明确指定,那就是吞吐量优先的垃圾收集器,也就是Parallel GC。

查看jvm的gc信息

gc统计信息

jstat -gc ${pid}

gc日志

在程序启动时添加参数,把gc日志打印到指定文件中

java -XX:+PrintGCDetails -Xloggc:./gc.log -jar math-game.jar

查看类加载信息

如何统计类的加载信息?详细统计一个运行中的程序,哪个类被哪个类加载器加载

Jstat命令:统计类加载信息,无法具体到类加载器

在这里插入图片描述

jcmd命令:统计类加载器的信息,无法统计到具体的类

在这里插入图片描述

arthus:可以列出某个类加载器加载的所有类,可以反编译某个类,

  • 第一步:classloader -l ,查看类加载器的哈希值
  • 第二步:classloader -c 哈希值 -a,查看指定类加载器加载的所有类

如果发生内存溢出 打印堆转储文件

在程序启动时添加jvm参数:

-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dumpfile_$(date +%Y%m%d_%H%M%S).hprof

如果启动时没有添加这个参数,那么即使发生内存溢出,也不会打印堆转储文件

踩坑记录

在mac上无法使用jinfo命令

问题:在macOs上无法使用jinfo命令,执行时报错,无法attach到进程上,我使用的jdk版本是 “1.8.0_411”,

原因:mac上jdk的小版本bug,需要升级jdk。参考这篇博客 https://www.jianshu.com/p/d30cc106894d

在mac上无法使用jmap命令

问题:在macOs上无法使用jmap命令,执行时报错,无法attach到进程上,我使用的jdk版本是 “1.8.0_411”,

原因:网上找方案,发现要升级jdk,这是java8在macOS上的一个bug,参考官网记录 https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8160376

在mac上启动jvisualvm时报错

报错信息:

The operation couldn’t be completed. Unable to locate a Java Runtime that supports jvisualvm.
Please visit http://www.java.com for information on installing Java.

原因是,jdk 1.8.0_361之后需要自行下载安装VisualVM,前往 https://visualvm.github.io 下载。参考这篇博客:https://blog.csdn.net/weixin_43859011/article/details/132805006

在mac上visualWM无法链接到java程序

在这里插入图片描述

点击Applications,就会出现本地进程和远程进程的对话框,可以选择某个进程。 // 这个图标确实不好找

在mac m系列芯片上安装MAT

MAT最新版需要使用Java17,我以为它不支持解析Java8的堆转储文件,但试了一下是支持的。所以先安装Java17、再安装MAT即可。

mat官网:https://eclipse.dev/mat/

历史版本下载页面:https://eclipse.dev/mat/download/previous/

参考

  • MAT的使用:https://juejin.cn/post/6911624328472133646
  • https://www.jianshu.com/p/f04c04ed462f

http://www.kler.cn/a/586851.html

相关文章:

  • 华为手机助手输入连接码时光标乱跳
  • 数据结构(C\C++)——算法复杂度
  • Django 分页操作详解
  • 【CodeMirror】系列(一)官网文档学习(二)核心扩展列表
  • Jetson Nano NX 重装系统
  • CSS3学习教程,从入门到精通, CSS3入门介绍的语法知识点及案例(1)
  • ssh通过22端口无法连接服务器问题处理
  • Python----数据可视化(Pyecharts三:绘图二:涟漪散点图,K线图,漏斗图,雷达图,词云图,地图,柱状图折线图组合,时间线轮廓图)
  • Java方法继承、方法重载、方法覆盖总结
  • Hive SQL 精进系列: IF 函数的强大功能与高级应用
  • Qlik Sense New Install with Restore
  • 【PlatformIO】基于Arduino的ESP8266 锂电池电压、电量测试
  • 射频前端模块(FEM)的基本原理与架构:从组成到WiFi路由器的应用
  • 1、操作系统引论
  • C语言 | 二叉树打印效果,控制台打印
  • 【Git学习笔记】Git初识及其结构原理分析(一)
  • JavaScript性能优化的几个方面入手
  • matlab 谐波分析公式绘图
  • Three.js 实现云状特效
  • Global Mapper 多功能的GIS软件