关于java:JVM知识梳理之四JVM运维与调试工具

4次阅读

共计 20807 个字符,预计需要花费 53 分钟才能阅读完成。

本章就 JVM 罕用的运维与调试工具做一些简略的梳理。

一、JDK 自带工具

本地环境装置过残缺 JDK 包之后,会在 $JAVA_HOME/bin 目录下发现很多工具,包含编译命令 javac、执行命令java 等。本节咱们梳理一下其中用于 JVM 运维的工具。

本节形容的 JDK 自带工具以 OpenJDK11 为准。

1.1 jps

jps 是 JVM 过程查找工具,相似于 linux 的 ps 命令。咱们应用这个命令次要是为了找到以后正在运行的 JVM 及其过程 ID。

jps 应用示例:

zhaochun@zhaochun-T480:bin$ jps
30816 Main
5842 Jps
484 Launcher
22137 TestCase01GCTest
32573 EchoBackService

jps 反对的参数选项:

选项 作用
-q 只输入 JVM 过程 ID
-m 输入启动 JVM 时,传给启动类 main 函数的参数
-l 输入启动类完整包名,如果是 jar 包,则输入 jar 门路
-v 输入 JVM 启动时传入的 JVM 参数

示例,启动如下这样一个 JVM

# JVM 参数:-Xms20M -Xmx20M -Xmn10M -XX:+UseG1GC -XX:MaxGCPauseMillis=200
# main 函数:czhao.study.jvm.TestCase01GCTest
# main 函数参数:testParam
/usr/java/jdk-11.0.7+10/bin/java -Xms20M -Xmx20M -Xmn10M -XX:+UseG1GC -XX:MaxGCPauseMillis=200 czhao.study.jvm.TestCase01GCTest testParam

# over

各种选项输入如下:

# 只输入 JVM 过程 ID
zhaochun@zhaochun-T480:bin$ jps -q
30816
484
10517
11116
32573

# 输入 main 函数参数
zhaochun@zhaochun-T480:bin$ jps -m | grep TestCase01GCTest
10517 TestCase01GCTest testParam

# 输入启动类完整包名
zhaochun@zhaochun-T480:bin$ jps -l | grep TestCase01GCTest
10517 czhao.study.jvm.TestCase01GCTest

# 输入 JVM 启动时传入参数
zhaochun@zhaochun-T480:bin$ jps -v | grep TestCase01GCTest
10517 TestCase01GCTest -Xms20M -Xmx20M -Xmn10M -XX:+UseG1GC -XX:MaxGCPauseMillis=200

1.2 jstat

jstat 是用于监督 JVM 各种运行时的状态信息的命令行工具,包含类加载、内存、垃圾收集、即时编译等运行时数据。

jstat 应用示例:

# 先应用 jps 查找指标 JVM 的过程 ID
zhaochun@zhaochun-T480:bin$ jps | grep TestCase01GCTest
10517 TestCase01GCTest

# 应用 jstat 查看指标过程 10517 的垃圾收集情况,每 500ms 查看一次,共查看 10 次
# 如果不加前面两个参数,则示意只查看一次
# -gc 示意查看的是垃圾收集情况
zhaochun@zhaochun-T480:bin$ jstat -gc 10517 500 10
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT   
 0.0   1024.0  0.0   1024.0  5120.0    0.0     14336.0    13816.0   4864.0 3446.8 512.0  305.8       1    0.001   0      0.000    0.001
 0.0   1024.0  0.0   1024.0  5120.0    0.0     14336.0    13816.0   4864.0 3446.8 512.0  305.8       1    0.001   0      0.000    0.001
 0.0   1024.0  0.0   1024.0  5120.0    0.0     14336.0    13816.0   4864.0 3446.8 512.0  305.8       1    0.001   0      0.000    0.001
 0.0   1024.0  0.0   1024.0  5120.0    0.0     14336.0    13816.0   4864.0 3446.8 512.0  305.8       1    0.001   0      0.000    0.001
 0.0   1024.0  0.0   1024.0  5120.0    0.0     14336.0    13816.0   4864.0 3446.8 512.0  305.8       1    0.001   0      0.000    0.001
 0.0   1024.0  0.0   1024.0  5120.0    0.0     14336.0    13816.0   4864.0 3446.8 512.0  305.8       1    0.001   0      0.000    0.001
 0.0   1024.0  0.0   1024.0  5120.0    0.0     14336.0    13816.0   4864.0 3446.8 512.0  305.8       1    0.001   0      0.000    0.001
 0.0   1024.0  0.0   1024.0  5120.0    0.0     14336.0    13816.0   4864.0 3446.8 512.0  305.8       1    0.001   0      0.000    0.001
 0.0   1024.0  0.0   1024.0  5120.0    0.0     14336.0    13816.0   4864.0 3446.8 512.0  305.8       1    0.001   0      0.000    0.001
 0.0   1024.0  0.0   1024.0  5120.0    0.0     14336.0    13816.0   4864.0 3446.8 512.0  305.8       1    0.001   0      0.000    0.001

上述 -gc 示意查看垃圾收集情况,其中各列的含意如下(内存空间单位:KB,工夫单位: 秒):

  • S0C:幸存者 0 区容量
  • S1C:幸存者 1 区容量
  • S0U:幸存者 0 区已应用大小
  • S1U:幸存者 1 区已应用大小
  • EC:eden 区容量
  • EU:eden 区已应用大小
  • OC:老年代容量
  • OU:老年代已应用大小
  • MC:元数据空间容量
  • MU:元数据空间已应用大小
  • CCSC:压缩类空间容量
  • CCSU:压缩类空间已应用大小
  • YGC:年老代 GC 次数
  • YGCT:年老代 GC 耗时共计
  • FGC:整堆 GC 次数
  • FGCT:整堆 GC 耗时共计
  • GCT:所有 GC 耗时共计

其中,CCS是压缩类空间,用于对象指针与类指针压缩,属于 MetaSpace 元数据空间的一部分,通过 -XX:+UseCompressedClassPointers-XX:+UseCompressedOops开启,默认开启。

-gc是参数选项,能够替换为以下其余选项:

选项 作用
-gc 监督 JVM 的堆情况,包含 eden 区、两个幸存者区、老年代的容量、已用空间、以及 GC 工夫共计等等
-gccapacity -gc 基本相同,但关注点在于 JVM 堆中各个区的历史最大、最小应用空间
-gcutil -gc 基本相同,但关注点在于已应用空间占总空间的百分比
-gccause -gcutil 基本相同,但会额定输入最近一次 GC 起因
-gcnew 监督新生代 GC 情况
-gcnewcapacity -gcnew 基本相同,但关注点在于历史最大最小已应用空间
-gcold 监督老年代 GC 情况
-gcoldcapacity -gcold 基本相同,但关注点在于历史最大最小已应用空间
-gcmetacapacity 监督元数据空间,重点关注历史最大最小已应用空间
-class 监督类加载、卸载数量、总空间及类装载消耗工夫
-compiler 输入即时编译器编译过的办法、耗时等
-printcompilation 输入曾经被即时编译的办法

1.3 jinfo

jinfo 用于查看 JVM 参数信息,并能够实时调整大量能够在运行时扭转的参数。

应用 jinfo 时要留神两点:

  • 执行 jinfo 命令的用户与指标 JVM 的启动用户应该是同一个用户,防止权限有余。
  • jinfo命令版本与指标 JVM 的 java 命令版本统一,应该是同一个 JDK 目录下的命令。

查看指标 JVM 的残缺 JVM 参数信息如下所示:

# 因为之前启动的 JVM 应用的是 /usr/java/jdk-11.0.7+10/bin/java,所以这里须要应用对应雷同目录下的 jinfo
zhaochun@zhaochun-T480:bin$ pwd
/usr/java/jdk-11.0.7+10/bin

zhaochun@zhaochun-T480:bin$ ./jinfo 10517
Java System Properties:
#Mon Feb 08 11:49:06 CST 2021
sun.desktop=gnome
awt.toolkit=sun.awt.X11.XToolkit
java.specification.version=11
sun.cpu.isalist=
sun.jnu.encoding=UTF-8
java.class.path=.\:/usr/java/jdk1.8.0_131/lib\:/usr/java/jdk1.8.0_131/jre/lib
java.vm.vendor=AdoptOpenJDK
sun.arch.data.model=64
java.vendor.url=https\://adoptopenjdk.net/
user.timezone=Asia/Shanghai
java.vm.specification.version=11
os.name=Linux
sun.java.launcher=SUN_STANDARD
user.country=CN
sun.boot.library.path=/usr/java/jdk-11.0.7+10/lib
sun.java.command=czhao.study.jvm.TestCase01GCTest testParam
jdk.debug=release
sun.cpu.endian=little
user.home=/home/zhaochun
user.language=zh
java.specification.vendor=Oracle Corporation
java.version.date=2020-04-14
java.home=/usr/java/jdk-11.0.7+10
file.separator=/
java.vm.compressedOopsMode=32-bit
line.separator=\n
java.vm.specification.vendor=Oracle Corporation
java.specification.name=Java Platform API Specification
java.awt.graphicsenv=sun.awt.X11GraphicsEnvironment
sun.management.compiler=HotSpot 64-Bit Tiered Compilers
java.runtime.version=11.0.7+10
user.name=zhaochun
path.separator=\:
os.version=4.15.0-20-generic
java.runtime.name=OpenJDK Runtime Environment
file.encoding=UTF-8
java.vm.name=OpenJDK 64-Bit Server VM
java.vendor.version=AdoptOpenJDK
java.vendor.url.bug=https\://github.com/AdoptOpenJDK/openjdk-support/issues
java.io.tmpdir=/tmp
java.version=11.0.7
user.dir=/home/work/sources/test/study-jvm/out/production/study-jvm
os.arch=amd64
java.vm.specification.name=Java Virtual Machine Specification
java.awt.printerjob=sun.print.PSPrinterJob
sun.os.patch.level=unknown
java.library.path=/usr/java/packages/lib\:/usr/lib64\:/lib64\:/lib\:/usr/lib
java.vendor=AdoptOpenJDK
java.vm.info=mixed mode
java.vm.version=11.0.7+10
sun.io.unicode.encoding=UnicodeLittle
java.class.version=55.0

VM Flags:
-XX:CICompilerCount=4 -XX:ConcGCThreads=2 -XX:G1ConcRefinementThreads=8 -XX:G1HeapRegionSize=1048576 -XX:GCDrainStackTargetSize=64 -XX:InitialHeapSize=20971520 -XX:MarkStackSize=4194304 -XX:MaxGCPauseMillis=200 -XX:MaxHeapSize=20971520 -XX:MaxNewSize=10485760 -XX:MinHeapDeltaBytes=1048576 -XX:NewSize=10485760 -XX:NonNMethodCodeHeapSize=5836300 -XX:NonProfiledCodeHeapSize=122910970 -XX:ProfiledCodeHeapSize=122910970 -XX:ReservedCodeCacheSize=251658240 -XX:+SegmentedCodeCache -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:+UseG1GC 

VM Arguments:
jvm_args: -Xms20M -Xmx20M -Xmn10M -XX:+UseG1GC -XX:MaxGCPauseMillis=200 
java_command: czhao.study.jvm.TestCase01GCTest testParam
java_class_path (initial): .:/usr/java/jdk1.8.0_131/lib:/usr/java/jdk1.8.0_131/jre/lib
Launcher Type: SUN_STANDARD

jinfo 的输入内容有三局部,别离是:

  • Java System Properties:JVM 运行时的环境变量
  • VM Flags:失效的虚拟机参数配置
  • VM Arguments:启动 JVM 时传入的参数、命令、及过后的对应会话的环境变量

这里要尤其留神的是,如果零碎环境上不止一个 JDK,比方这里的例子,理论有两个 JDK,一个 Java8 一个 Java11,那么运行 JVM 时应用的 JDK 版本应该看 Java System Properties 里的 java.runtime.version,而不是VM Arguments 里的java_class_path (initial)

能够应用 jinfo -sysprops <pid> 只查看 JVM 运行时的环境变量。

能够应用 jinfo -flags <pid> 只查看失效的虚拟机参数配置,比方jinfo -flags 10517

能够应用 jinfo -flag <name> <pid> 查看指定参数的值,比方jinfo -flag MaxHeapSize 10517

能够应用 jinfo -flag [+|-]<name> <pid> 实时开启或敞开某个能够在运行时扭转的参数,比方jinfo -flag +HeapDumpOnOutOfMemoryError 10517

能够应用 jinfo -flag <name>=<value> 实时批改某个能够在运行时扭转的参数的值,比方jinfo -flag MaxHeapFreeRatio=75 10517

通过命令 java -XX:+PrintFlagsFinal <pid> | grep manageable 查看哪些参数能够在运行时扭转。

1.4 jmap

jmap 是 java 内存映像工具,次要用于查问以后堆和办法区的详细信息,生成堆的快照文件等。个别都是应用 -XX:+HeapDumpOnOutOfMemoryError 参数指定 JVM 在内存溢出异样时主动生成堆的快照文件。之后在服务器产生内存溢出异样时,将对应的快照文件拉取到本地应用工具剖析。

jmap 的堆转储快照文件有很多工具能够剖析,罕用的有 MAT,Jprofiler,IBM HeapAnalyzer 等,后续章节会一一介绍。

同 jinfo 一样,jmap 在应用时,也要留神用户和 JDK 版本是否与指标 JVM 统一。

能够应用 jmap -histo[:live] <pid> 剖析以后对中对象,例如./jmap -histo:live 10517

能够应用 jmap -dump:[live,]format=b,file=<file> <pid> 对指标 JVM 进行快照转储,例如:

# 只转贮存活对象
./jmap -dump:live,format=b,file=/home/work/sources/test/study-jvm/output/jmap/dump001 10517

# 转储所有对象
./jmap -dump:format=b,file=/home/work/sources/test/study-jvm/output/jmap/dump002 10517

1.5 jhat

jhat 是一个原始简陋的用来剖析 jmap 的堆转储快照文件的工具,Java9 之后曾经被移出 JDK 的工具包。这里理解一下即可。

咱们用 Java8 的 jhat 工具对方才 jmap 转储的快照文件进行剖析:

zhaochun@zhaochun-T480:jmap$ jhat dump001
Reading from dump001...
Dump file created Mon Feb 08 12:37:58 CST 2021
Snapshot read, resolving...
Resolving 28806 objects...
Chasing references, expect 5 dots.....
Eliminating duplicate references.....
Snapshot resolved.
Started HTTP server on port 7000
Server is ready.

而后拜访 http://localhost:7000/ 就能够看到这个简陋的剖析页面。

1.6 jstack

jstack 用于生成虚拟机以后时刻的线程快照。生成线程快照次要是为了定位长时间进展的线程,比方线程间死锁、死循环、申请内部资源超时等等。通过 jstack 能够查看到各个线程的调用堆栈信息,就能够晓得线程目前运行在哪一句代码,在做什么事件或者期待什么资源。

同样的,jstack 应用时,也要留神用户、版本是否与指标 JVM 统一。

应用示例:

zhaochun@zhaochun-T480:bin$ jstack 3031
2021-02-08 15:11:01
Full thread dump OpenJDK 64-Bit Server VM (11.0.7+10 mixed mode):

Threads class SMR info:
_java_thread_list=0x00007fd8500b8430, length=12, elements={
0x00007fd8d0016000, 0x00007fd8d026b800, 0x00007fd8d026f800, 0x00007fd8d0284800,
0x00007fd8d0286800, 0x00007fd8d0288800, 0x00007fd8d028a800, 0x00007fd8d030f000,
0x00007fd8d0320800, 0x00007fd878001000, 0x00007fd86417b800, 0x00007fd850042800
}

"main" #1 prio=5 os_prio=0 cpu=135.52ms elapsed=2030.68s tid=0x00007fd8d0016000 nid=0xbd8 runnable  [0x00007fd8d987a000]
   java.lang.Thread.State: RUNNABLE
    at java.io.FileInputStream.readBytes(java.base@11.0.7/Native Method)
    at java.io.FileInputStream.read(java.base@11.0.7/FileInputStream.java:279)
    at java.io.BufferedInputStream.read1(java.base@11.0.7/BufferedInputStream.java:290)
    at java.io.BufferedInputStream.read(java.base@11.0.7/BufferedInputStream.java:351)
    - locked <0x000000062ac01148> (a java.io.BufferedInputStream)
    at sun.nio.cs.StreamDecoder.readBytes(java.base@11.0.7/StreamDecoder.java:284)
    at sun.nio.cs.StreamDecoder.implRead(java.base@11.0.7/StreamDecoder.java:326)
    at sun.nio.cs.StreamDecoder.read(java.base@11.0.7/StreamDecoder.java:178)
    - locked <0x000000062ac01a78> (a java.io.InputStreamReader)
    at java.io.InputStreamReader.read(java.base@11.0.7/InputStreamReader.java:185)
    at java.io.Reader.read(java.base@11.0.7/Reader.java:189)
    at java.util.Scanner.readInput(java.base@11.0.7/Scanner.java:882)
    at java.util.Scanner.findWithinHorizon(java.base@11.0.7/Scanner.java:1796)
    at java.util.Scanner.nextLine(java.base@11.0.7/Scanner.java:1649)
    at czhao.study.jvm.TestCase01GCTest.main(TestCase01GCTest.java:15)

"Reference Handler" #2 daemon prio=10 os_prio=0 cpu=0.72ms elapsed=2030.63s tid=0x00007fd8d026b800 nid=0xbe0 waiting on condition  [0x00007fd8a065e000]
   java.lang.Thread.State: RUNNABLE
    at java.lang.ref.Reference.waitForReferencePendingList(java.base@11.0.7/Native Method)
    at java.lang.ref.Reference.processPendingReferences(java.base@11.0.7/Reference.java:241)
    at java.lang.ref.Reference$ReferenceHandler.run(java.base@11.0.7/Reference.java:213)

"Finalizer" #3 daemon prio=8 os_prio=0 cpu=0.37ms elapsed=2030.63s tid=0x00007fd8d026f800 nid=0xbe1 in Object.wait()  [0x00007fd8a055d000]
   java.lang.Thread.State: WAITING (on object monitor)
    at java.lang.Object.wait(java.base@11.0.7/Native Method)
    - waiting on <0x000000062ac03180> (a java.lang.ref.ReferenceQueue$Lock)
    at java.lang.ref.ReferenceQueue.remove(java.base@11.0.7/ReferenceQueue.java:155)
    - waiting to re-lock in wait() <0x000000062ac03180> (a java.lang.ref.ReferenceQueue$Lock)
    at java.lang.ref.ReferenceQueue.remove(java.base@11.0.7/ReferenceQueue.java:176)
    at java.lang.ref.Finalizer$FinalizerThread.run(java.base@11.0.7/Finalizer.java:170)

"Signal Dispatcher" #4 daemon prio=9 os_prio=0 cpu=0.55ms elapsed=2030.63s tid=0x00007fd8d0284800 nid=0xbe2 runnable  [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread0" #5 daemon prio=9 os_prio=0 cpu=5100.91ms elapsed=2030.63s tid=0x00007fd8d0286800 nid=0xbe3 waiting on condition  [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
   No compile task

"C1 CompilerThread0" #8 daemon prio=9 os_prio=0 cpu=1492.77ms elapsed=2030.63s tid=0x00007fd8d0288800 nid=0xbe4 waiting on condition  [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
   No compile task

"Sweeper thread" #9 daemon prio=9 os_prio=0 cpu=26.09ms elapsed=2030.63s tid=0x00007fd8d028a800 nid=0xbe5 runnable  [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Service Thread" #10 daemon prio=9 os_prio=0 cpu=12.23ms elapsed=2030.60s tid=0x00007fd8d030f000 nid=0xbe6 runnable  [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Common-Cleaner" #11 daemon prio=8 os_prio=0 cpu=3.01ms elapsed=2030.60s tid=0x00007fd8d0320800 nid=0xbe8 in Object.wait()  [0x00007fd893167000]
   java.lang.Thread.State: TIMED_WAITING (on object monitor)
    at java.lang.Object.wait(java.base@11.0.7/Native Method)
    - waiting on <0x000000062ac047a8> (a java.lang.ref.ReferenceQueue$Lock)
    at java.lang.ref.ReferenceQueue.remove(java.base@11.0.7/ReferenceQueue.java:155)
    - waiting to re-lock in wait() <0x000000062ac047a8> (a java.lang.ref.ReferenceQueue$Lock)
    at jdk.internal.ref.CleanerImpl.run(java.base@11.0.7/CleanerImpl.java:148)
    at java.lang.Thread.run(java.base@11.0.7/Thread.java:834)
    at jdk.internal.misc.InnocuousThread.run(java.base@11.0.7/InnocuousThread.java:134)

"Attach Listener" #12 daemon prio=9 os_prio=0 cpu=276.95ms elapsed=2027.53s tid=0x00007fd878001000 nid=0xc0a waiting on condition  [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"RMI TCP Accept-0" #14 daemon prio=9 os_prio=0 cpu=3.55ms elapsed=2026.03s tid=0x00007fd86417b800 nid=0xc16 runnable  [0x00007fd89202d000]
   java.lang.Thread.State: RUNNABLE
    at java.net.PlainSocketImpl.socketAccept(java.base@11.0.7/Native Method)
    at java.net.AbstractPlainSocketImpl.accept(java.base@11.0.7/AbstractPlainSocketImpl.java:458)
    at java.net.ServerSocket.implAccept(java.base@11.0.7/ServerSocket.java:565)
    at java.net.ServerSocket.accept(java.base@11.0.7/ServerSocket.java:533)
    at sun.management.jmxremote.LocalRMIServerSocketFactory$1.accept(jdk.management.agent@11.0.7/LocalRMIServerSocketFactory.java:52)
    at sun.rmi.transport.tcp.TCPTransport$AcceptLoop.executeAcceptLoop(java.rmi@11.0.7/TCPTransport.java:394)
    at sun.rmi.transport.tcp.TCPTransport$AcceptLoop.run(java.rmi@11.0.7/TCPTransport.java:366)
    at java.lang.Thread.run(java.base@11.0.7/Thread.java:834)

"RMI Scheduler(0)" #16 daemon prio=9 os_prio=0 cpu=4.74ms elapsed=2025.98s tid=0x00007fd850042800 nid=0xc19 waiting on condition  [0x00007fd891e2b000]
   java.lang.Thread.State: WAITING (parking)
    at jdk.internal.misc.Unsafe.park(java.base@11.0.7/Native Method)
    - parking to wait for  <0x000000062ac02540> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
    at java.util.concurrent.locks.LockSupport.park(java.base@11.0.7/LockSupport.java:194)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(java.base@11.0.7/AbstractQueuedSynchronizer.java:2081)
    at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(java.base@11.0.7/ScheduledThreadPoolExecutor.java:1170)
    at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(java.base@11.0.7/ScheduledThreadPoolExecutor.java:899)
    at java.util.concurrent.ThreadPoolExecutor.getTask(java.base@11.0.7/ThreadPoolExecutor.java:1054)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(java.base@11.0.7/ThreadPoolExecutor.java:1114)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(java.base@11.0.7/ThreadPoolExecutor.java:628)
    at java.lang.Thread.run(java.base@11.0.7/Thread.java:834)

"VM Thread" os_prio=0 cpu=194.73ms elapsed=2030.64s tid=0x00007fd8d0263000 nid=0xbde runnable  

"GC Thread#0" os_prio=0 cpu=98.26ms elapsed=2030.68s tid=0x00007fd8d0040800 nid=0xbd9 runnable  

"GC Thread#1" os_prio=0 cpu=7.07ms elapsed=847.66s tid=0x00007fd888001000 nid=0x466f runnable  

"GC Thread#2" os_prio=0 cpu=7.05ms elapsed=847.66s tid=0x00007fd888002000 nid=0x4670 runnable  

"GC Thread#3" os_prio=0 cpu=7.40ms elapsed=847.66s tid=0x00007fd888003800 nid=0x4671 runnable  

"GC Thread#4" os_prio=0 cpu=2.72ms elapsed=847.66s tid=0x00007fd888005000 nid=0x4672 runnable  

"GC Thread#5" os_prio=0 cpu=5.62ms elapsed=847.66s tid=0x00007fd888006800 nid=0x4673 runnable  

"GC Thread#6" os_prio=0 cpu=7.15ms elapsed=847.66s tid=0x00007fd888008000 nid=0x4674 runnable  

"GC Thread#7" os_prio=0 cpu=1.41ms elapsed=847.66s tid=0x00007fd88800a000 nid=0x4675 runnable  

"G1 Main Marker" os_prio=0 cpu=1.09ms elapsed=2030.67s tid=0x00007fd8d007a800 nid=0xbda runnable  

"G1 Conc#0" os_prio=0 cpu=0.19ms elapsed=2030.67s tid=0x00007fd8d007c800 nid=0xbdb runnable  

"G1 Refine#0" os_prio=0 cpu=0.91ms elapsed=2030.67s tid=0x00007fd8d01fe000 nid=0xbdc runnable  

"G1 Young RemSet Sampling" os_prio=0 cpu=367.00ms elapsed=2030.67s tid=0x00007fd8d01ff800 nid=0xbdd runnable  
"VM Periodic Task Thread" os_prio=0 cpu=1578.99ms elapsed=2030.60s tid=0x00007fd8d0311800 nid=0xbe7 waiting on condition  

JNI global refs: 38, weak refs: 0

jstack 反对的可选参数:

  • -F:强制输入线程堆栈
  • -l:额定输入锁的附加信息
  • -m:如果调用了本地办法,输入 C /C++ 堆栈

1.7 jcmd

jcmd 提供了上述命令行工具的对立应用形式,如下表所示:

与 jcmd 具备相似性能的还有 jhsdb,也一起列在上面的表中。jhsdb 还提供了图形化性能,在下一节介绍。

命令 jcmd 命令 jhsdb 命令
jps -lm jcmd
jmap -dump <pid> jcmd <pid> GC.heap_dump jhsdb jmap –binaryheap –pid <pid>
jmap -histo <pid> jcmd <pid> GC.class_histogram jhsdb jmap –histo –pid <pid>
jstack <pid> jcmd <pid> Thread.print jhsdb jstack –locks –pid <pid>
jinfo -sysprops <pid> jcmd <pid> VM.system_properties jhsdb info –sysprops –pid <pid>
jinfo -flags <pid> jcmd <pid> VM.flags jhsdb jinfo –flags –pid <pid>

同样的,jcmd 应用时,也要留神用户、版本是否与指标 JVM 统一。

1.7 jhsdb

jhsdb 除了命令以外,还提供了图形化性能来监督剖析 JVM 情况。

jhsdb 应用如下命令开启指标 JVM 的图形化剖析界面:

jhsdb hsdb --pid <pid>

同样的,jhsdb 应用时,也要留神用户、版本是否与指标 JVM 统一。

关上后首先有一个线程窗口,展示以后的线程信息。

另外 tools 菜单下有很多性能按钮,比方 Class Browser 能够查看所有类信息,Heap Parameters能够输入以后堆内存分区应用状况就像上面这样:

G1 Heap:
   regions  = 20
   capacity = 20971520 (20.0MB)
   used     = 15204416 (14.50006103515625MB)
   free     = 5767104 (5.49993896484375MB)
   72.50030517578125% used
G1 Young Generation:
Eden Space:
   regions  = 0
   capacity = 5242880 (5.0MB)
   used     = 0 (0.0MB)
   free     = 5242880 (5.0MB)
   0.0% used
Survivor Space:
   regions  = 1
   capacity = 1048576 (1.0MB)
   used     = 1048576 (1.0MB)
   free     = 0 (0.0MB)
   100.0% used
G1 Old Generation:
   regions  = 14
   capacity = 14680064 (14.0MB)
   used     = 14155840 (13.50006103515625MB)
   free     = 524224 (0.49993896484375MB)
   96.42900739397321% used

还有很多其余性能,大家能够缓缓尝试。

1.8 jconsole

jconsole 是一款对 JVM 的可视化监督管理工具,通过它咱们能够实时地监督 JVM 的内存、线程、类加载等信息的变化趋势。

应用上面的命令启动:

jconsole

同样的,jconsole 应用时,也要留神用户、版本是否与指标 JVM 统一。

jconsole 启动后有一个抉择 JVM 过程的界面,抉择一个 JVM 后即可看到界面。

如果要连贯一个近程机器上的 JVM,那么须要在指标机器的 JVM 上减少以下参数:

-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.port=<port>
-Djava.rmi.server.hostname=<ip>

如果想要应用明码认证和 ssl 保障通信安全,请自行搜寻。

二、Java9 后须要独自下载的 JDK 工具

Java9 之后,局部 JVM 工具被移出了 JDK 工具包,须要独自去装置。这里介绍两个比拟好用的工具。

2.1 visualVM

visualVM 是一个相似 jconsole,然而比 jconsole 的性能更弱小丰盛的 JVM 监督工具,还能够用来剖析 jmap 的堆转储快照文件。它也能够连贯近程 JVM,办法与 jconsole 的近程 JVM 一样。

visualVM 的下载地址是:

https://visualvm.github.io/download.html

下载后启动:

./visualvm --jdkhome /usr/java/jdk-11.0.7+10

同样的,visualVM 应用时,也要留神用户、JDK 版本是否与指标 JVM 统一。

visualVM 监督信息比 jconsole 更丰盛,而且它还能够间接导入 jmap 的堆转储快照文件,并依照你须要的维度做一些简略的排序展现。

2.2 JMC

JMC,Java Mission Control,是另一个很弱小的 JVM 监督工具,和 visualVM、jconsole 一样,它能够监督 JVM 的各种数据。除此以外,它还提供了弱小的 航行记录器 性能,记录一段时间内 JVM 的各种信息,包含内存、代码、线程、IO、事件等等的记录,而后基于这些信息做性能剖析。剖析后果如下图所示:

JMC 下载地址如下:

https://www.oracle.com/java/technologies/javase/products-jmc7-downloads.html

本地启动命令:

./jmc

页面启动后,左侧会显示本地 JVM,如果要连贯近程 JVM,能够在 文件 --> 连贯 菜单中创立新连贯。近程 JVM 的参数配置与 jconsole 一样。

间接点击左侧菜单的指标 JVM 即可关上实时监督页面,双击左侧菜单该 JVM 上层的 航行记录器 即可开始一次航行记录,完结后会主动给出剖析报告。

Flight Recorder航行记录,简称 JFR,以前是商业 JDK 的个性,起初在 JDK11 中开源,通常能够通过 JVM 启动参数-XX:StartFlightRecording 开启,或者通过 jcmd 相干命令录制。这里通过 JMC 工具能够可视化录制航行记录。

JFR 是一种用于收集对于正在运行的 Java 应用程序的诊断和剖析数据的工具。它集成到 Java 虚拟机 (JVM) 中,简直不会造成性能开销,因而即便在负载十分大的生产环境中也能够应用它。它收集 JVM 的各种事件信息,包含:磁盘 IO、GC、线程 sleep、线程 wait、Socket read/write 等。JFR 就如同飞机上的黑匣子,通过收集的这些事件的详细信息可能更加深刻理解程序的外部运行过程,这是很多其余工具所不具备的。

除了实时 JVM 监督和航行记录剖析以外,JMC 也能够间接关上并剖析 jmap 的堆转储快照文件,剖析后果如下:

三、其余工具

3.1 MAT

MAT,全称Memory Analyzer Tool,它是一个傻瓜式的堆转储快照文件剖析工具,既能够本人生成堆转储快照文件,也能够间接剖析 jmap 命令导出的快照文件。

MAT 工具能够提供以下剖析:

  • Histogram:列出内存中的对象,对象的个数以及大小
  • Dominator Tree:列出最大的对象以及其依赖存活的 Object
  • Top Consumers:通过图形列出最大的 object
  • duplicate classes:检测由多个类装载器加载的类
  • Leak Suspects:内存透露剖析
  • Top Components:列出大于总堆数的百分之 1 的报表
  • Component Report:剖析属于同一个包或者被同一个类加载器加载的对象情况

其中最罕用的就是 Leak Suspects 内存透露剖析。

MAT 须要独自下载安装,下载地址:

https://www.eclipse.org/mat/downloads.php

MAT 是 eclipse 系的工具,相似与 Jprofiler 之于 IDEA,但它是收费的,不像 Jprofiler 还免费。。。

下载到本地解压后间接运行 MemoryAnalyzer 即可。

应用也很简略,首页点击Open heap dump,而后抉择对应的堆转储快照文件即可。

咱们个别是在 JVM 启动参数增加 -XX:+HeapDumpOnOutOfMemoryError 让 JVM 在产生内存溢出异样时主动 dump 堆快照文件,所以剖析时最重要的就是找出数量和空间耗费最大的对象信息,并通过调用堆栈信息查找可能产生内存泄露的代码所在。

而通过 MAT 的 Leak Suspects 性能,就能间接给出一个内存泄露剖析报告,如下所示:

同时报告还会指出最可能产生泄露的具体对象及其调用堆栈信息:

更多相干信息请查看官网文档:

http://wiki.eclipse.org/Memor…

3.2 IBM HeapAnalyzer

MAT 在剖析内存泄露时,尽管能疾速定位产生内存溢出异样时占用空间最多的对象,但这些对象往往是很底层的对象,咱们要通过堆栈调用去找到真正代码中产生泄露的中央。而 MAT 貌似没有直观展示调用关系树的性能,这时咱们能够应用 IBM HeapAnalyzer 这个工具。

IBM HeapAnalyzer的下载和应用参考上面的地址:

https://www.ibm.com/support/pages/node/1109955?mhsrc=ibmsearch_a&mhq=heapanalyzer

它与 MAT 一样,也能够主动做内存泄露剖析,比 MAT 更好的中央是,它对堆栈调用做了可视化的转换,能够更直观地看到调用关系树。

IBM HeapAnalyzer最大的问题是,它曾经很久没有更新保护了。。。

3.3 Jprofiler

JProfiler 是由 ej-technologies 公司开发的一款性能瓶颈剖析工具。它是一款优良的商业软件,性能十分丰盛,因而具备一些免费软件所不具备的性能。Jprofiler 提供的次要性能有内存视图、CPU 视图、线程视图、堆遍历器 (Heap Walker) 等。

它与 MAT,IBM HeapAnalyzer 一样,能够用于剖析堆转储快照文件。但因为免费,这里就不介绍了。

3.3 Arthas

后面所有的 JVM 工具,都是基于 JVM 本人提供的 MBeans/JMX 技术,或者 JFR 技术去监督 JVM 状态。要么是监督 JVM 运行时数据,要么是 OOM 之后的堆转储快照文件的离线剖析。

如果咱们要找到一个 JVM 运行时的性能瓶颈所在,咱们须要监督运行时内存、线程、CPU 等资源的变动,并找到对应时间段的对象或线程来定位具体比拟耗费资源的代码。这种形式实际操作起来还是很麻烦的。

而 Arthas 就是一个在实时监督跟踪具体方法方面特地弱小的一种 JVM 在线调试工具。Arthas 提供了在线的办法级别的监督跟踪性能。比方 monitor/watch/trace 等指令,通过字节码加强技术,间接在代码字节码层面做插桩,实现对运行时办法调用链、耗时、返回数据等信息的动静实时监督。

Arthas 的装置与应用能够间接参考官网文档,上手很简略:

https://arthas.aliyun.com/doc/index.html
https://arthas.aliyun.com/doc/quick-start.html
https://arthas.aliyun.com/doc/advanced-use.html

除了 JMC 的 JFR 航行记录,其余 JVM 工具所能监督的信息,基本上 Arthas 也都具备对应性能。而除了这些 JVM 信息查看、运行时情况监督。以及办法的实时监督跟踪之外,Arthas 还有在线编译与反编译的性能,对于某些场景下的长期验证,或确认部署版本是否正确等非凡需要而言,非常不便。

但 arthas 在应用时,会对 JVM 的字节码造成入侵,会占用局部资源,对系统整体性能有肯定的影响。所以相比于其余工具,arthas 其实是一个开发人员的调试工具,而不是 JVM 运维工具。

四、JVM 工具总结

JVM 运维与调试工具当然并不是仅仅只有本章节所列举的这些,但一般而言,这些工具把握局部也就满足平时的须要了。这里对它们进行一个简略的总结。

  • 当你只是简略地查看 JVM 运行时的情况时,你能够间接应用 JDK 自带的那些工具命令,比方 jpsjinfo 等等。
  • 当你须要在 OOM 时查看内存泄露起因时,能够间接在 JVM 参数中配置 OOM 主动 dump 堆转储快照文件,并配合 jmap 等工具手动或定时周期性地 dump 堆快照。
  • 当你想实时监督 JVM 的内存、线程、CPU 等资源耗费趋势时,你能够应用 jconsolevisualVMJMC 等工具。
  • 当你想全面监督 JVM 各种事件信息,包含磁盘 IO、GC、线程 sleep、线程 wait、Socket read/write 等等,且不想对 JVM 性能带去影响时,你能够通过 JMC 录制 JFR 航行记录,并在 JMC 中查看报告。
  • 当你须要在办法层面上监督跟踪其调用链路,耗时及返回值时,你能够应用 arthas 这样的在线 JVM 调试工具。

集体认为作为保底伎俩,无论是 JVM 运维还是开发调试,JDK 自带的那些工具比方 jpsjinfojstatjstack 等,咱们都应该要学习如何应用。

对于偏差于 JVM 运维的可视化监督方面,举荐应用 JMC,并尝试录制 JFR 航行记录。对于业余的 JVM 运维,以及编写各种性能剖析报告来说,这个工具很有用。

而对于开发人员来说,目前最举荐的工具是arthas,基本上 JVM 调试须要的性能它都有。

正文完
 0