工欲善其事, 必先利其器. 关于确诊 Java 运用来说, 咱们有哪些有用的东西呢? 许多人可能很快想到网上有分析 Java verbose GC 日志的东西, 还有分析 Java 线程的东西, 有分析 Heap dump的东西, 有主动反编译Java 代码的东西, 还有多对 Java 运用做 profiling 的东西. 其实 JDK 自身自带了一些十分有用的东西, 这些东西自身都是 JDK 开发者自己觉得有用, 然后公开出来的, 所以, 咱们先从这些最基本的东西看起, 能够协助咱们从各个旁边面了解 Java 运用.

JDK 自带的东西都在 JDK 的 bin 目录, 比方下面便是 JDK 17 的 bin 目录所带的一切实用东西:

eric@supra jdk17 %  ls bin
jar		javadoc		jdb		jimage		jmod		jshell		keytool
jarsigner	javap		jdeprscan	jinfo		jpackage	jstack		rmiregistry
java		jcmd		jdeps		jlink		jps		jstat		serialver
javac		jconsole	jfr		jmap		jrunscript	jstatd

下面咱们就介绍并实践一些有关确诊Java运用的指令, 有些在后面的实际操作中将会多次用到.

jps

首先咱们要看的是这个这个最简略的指令, 它的作用很简略, 便是找出当时机器或许 container 里面的 Java 运用. 比方:

eric@supra ~ %  jps
86064 Jps
7985

简略的 jps 给出了当时机器上有2个正在运转的 Java 运用程序的进程id, 其间榜首个是 Jps 指令自身, 因为它自己也是一个 Java 运用程序, 当它自己运转的时分, 它也会检测到它自己, 并且当你运用 Jps 的时分, 你永远会看到它自己. 别的一个进程号便是别的一个正在运转的 Java 运用程序.

所以, 它的功用便是找出你要找的Java 运用的进程号, 因为许多其它指令都是要知道 Java 进程号, 然后才干做进一步操足的. 这个找出进程号的功用很相似 Linux 平台上的 pgrep java 这个操作.

jinfo

jinfo 首要用来帮咱们检查当时某个正在运转的 JVM 的 VM flags 和 体系特点. 比方咱们想检查这运转的 Java 进程运用的JDK 版别, 或许它运用的体系文件路径分隔符, 或许这个VM enable 了哪些flags, jinfo 都能帮咱们打印出来. 比方下面咱们对某个 Java 进程运用 jinfo 指令的结果(因为篇幅所限, 省略了许多):

eric@supra ~ %  jinfo 7985
Java System Properties:
#Sun Feb 26 09:18:10 MST 2023
file.encoding=UTF-8
file.separator=/
java.class.path=/Applications/PyCharm CE.app/Contents/lib/junit4.jar\:/Applications/PyCharm CE.app/Contents/lib/platform-objectSerializer-annotations.jar\:/Applications/PyCharm CE.app/Contents/lib/rd.jar\:/Applications/PyCharm CE.app/Contents/lib/util_rt.jar
java.class.version=61.0
java.home=/Applications/PyCharm CE.app/Contents/jbr/Contents/Home
java.io.tmpdir=/var/folders/w7/bjbwyqmn56j8k1j6mqg4yys00000gq/T/
os.arch=x86_64
os.name=Mac OS X
os.version=13.2.1
path.separator=\:
user.country=CN
user.dir=/
user.timezone=America/Denver
VM Flags:
 -XX:ConcGCThreads=3 -XX:G1ConcRefinementThreads=13 -XX:G1EagerReclaimRemSetThreshold=8 -XX:G1HeapRegionSize=1048576 -XX:GCDrainStackTargetSize=64 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/Users/xiatian/java_error_in_pycharm.hprof -XX:+IgnoreUnrecognizedVMOptions -XX:InitialHeapSize=134217728 -XX:MarkStackSize=4194304 -XX:MaxHeapSize=2147483648 -XX:MaxNewSize=1287651328 -XX:MinHeapDeltaBytes=1048576 -XX:MinHeapSize=134217728 -XX:NonNMethodCodeHeapSize=5826188 -XX:NonProfiledCodeHeapSize=265522362 -XX:-OmitStackTraceInFastThrow -XX:ProfiledCodeHeapSize=265522362 -XX:ReservedCodeCacheSize=536870912 -XX:+SegmentedCodeCache -XX:SoftMaxHeapSize=2147483648 -XX:SoftRefLRUPolicyMSPerMB=50 -XX:SweeperThreshold=0.234375 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:+UseG1GC -XX:-UseNUMA -XX:-UseNUMAInterleaving
VM Arguments:
jvm_args: -Xms128m -Xmx750m -XX:ReservedCodeCacheSize=512m -XX:+UseG1GC -XX:SoftRefLRUPolicyMSPerMB=50 -XX:CICompilerCount=2 -XX:+HeapDumpOnOutOfMemoryError -XX:-OmitStackTraceInFastThrow -XX:+IgnoreUnrecognizedVMOptions 
java_command: <unknown>
java_class_path (initial): /Applications/PyCharm CE.app/Contents/lib/platform-objectSerializer-annotations.jar:/Applications/PyCharm CE.app/Contents/lib/rd.jar:/Applications/PyCharm CE.app/Contents/lib/util_rt.jar
Launcher Type: generic

除了检查 JVM 的flags 和体系特点之外, 它还能够设置某些 VM 的 flags. 其间某些是开关类的 flag, 都是能够经过 +/- 号来翻开/封闭的. 别的一些设值的, 需求设置 name=value的方式设置. 比方:

eric@supra ~ %  jinfo -flag HeapDumpBeforeFullGC 7985
-XX:-HeapDumpBeforeFullGC
eric@supra ~ %  jinfo -flag +HeapDumpBeforeFullGC 7985
eric@supra ~ %  jinfo -flag HeapDumpBeforeFullGC 7985
-XX:+HeapDumpBeforeFullGC

上面的比方中, 关于7985这个进程来说, 咱们先打印出 HeapDumpBeforeFullGC 这个 flag 的当时值, 这个 flag 表明的意义是: 在做 Full GC 之前, 发生一个heap dump, 它默许是封闭的(运用-表明), 然后, 咱们运用 jinfo 给它设置翻开开关, 然后再打印这个flag的新状况, 就看到它是翻开的了.

一个设置值的比方:

eric@supra ~ % jinfo -flag MinHeapFreeRatio 7985
-XX:MinHeapFreeRatio=40
eric@supra ~ %  jinfo -flag MinHeapFreeRatio=35 7985
eric@supra ~ %  jinfo -flag MinHeapFreeRatio 7985
-XX:MinHeapFreeRatio=35

上面的比方中, 咱们先输出了进程 7985 的 MinHeapFreeRatio 的当时值, 它当时值是40, 意思是当发生一个GC后, 最少要有40%的闲暇比率, 才不会申请新的heap空间, 不然, 则回去扩展heap的巨细. 第二条指令, 咱们更改这个值到35%. 第三条指令显示咱们改后的值.

下面是 jinfo 的指令阐明, 能够看到, 它能更改 VM 的flag, 却不能更改JVM 的体系特点.

eric@supra ~ % jinfo --help
Usage:
    jinfo <option> <pid>
       (to connect to a running process)
where <option> is one of:
    -flag <name>         to print the value of the named VM flag
    -flag [+|-]<name>    to enable or disable the named VM flag
    -flag <name>=<value> to set the named VM flag to the given value
    -flags               to print VM flags
    -sysprops            to print Java system properties
    <no option>          to print both VM flags and system properties
    -? | -h | --help | -help to print this help message

那么是任何的 VM 的flag 咱们都能更改吗? 其实并不是, 只需某些 flag 能够改, 别的一些不能改, 比方 heap 的总巨细是一开始就确认的, 等程序运转起来, 只能检查这个值, 不能更改.

哪么哪些 flag 能在运转时分, 更改呢? 这个依据不同的 java 版别有少许不同, 不过关于特定版别的 java, 咱们能够经过 java -XX:+PrintFlagsFinal --version 来检查一切的 flags, 其间标注有 manageable 的, 都是能够更改的. 比方下面是我 JDK 17 版别的上面能够更改的 flag 列表. 榜首列是 flag 的类型, 第二列是 flag 的姓名, 第三列是 flag 的默许值, 最终一列是 flag 的特点, 比方 manageable 表明能够改的, pd 表明平台相关的(platform dependence), default 表明有默许值.

 eric@supra ~ % java -XX:+PrintFlagsFinal --version | grep manage
    uintx G1PeriodicGCInterval                     = 0                                      {manageable} {default}
   double G1PeriodicGCSystemLoadThreshold          = 0.000000                               {manageable} {default}
     bool HeapDumpAfterFullGC                      = false                                  {manageable} {default}
     bool HeapDumpBeforeFullGC                     = false                                  {manageable} {default}
     intx HeapDumpGzipLevel                        = 0                                      {manageable} {default}
     bool HeapDumpOnOutOfMemoryError               = false                                  {manageable} {default}
    ccstr HeapDumpPath                             =                                        {manageable} {default}
    uintx MaxHeapFreeRatio                         = 70                                     {manageable} {default}
    uintx MinHeapFreeRatio                         = 40                                     {manageable} {default}
     bool PrintClassHistogram                      = false                                  {manageable} {default}
     bool PrintConcurrentLocks                     = false                                  {manageable} {default}
     bool ShowCodeDetailsInExceptionMessages       = true                                   {manageable} {default}
   size_t SoftMaxHeapSize                          = 8589934592                             {manageable} {ergonomic}

jstat

jstat 指令输出体系的一些计算信息, 经过 jstat -options指令, 咱们能够看到有哪些信息能够输出:

 eric@supra ~ % jstat -options
-class
-compiler
-gc
-gccapacity
-gccause
-gcmetacapacity
-gcnew
-gcnewcapacity
-gcold
-gcoldcapacity
-gcutil
-printcompilation

上面是 JDK 17 能够支持输出的一些计算, 能够看到首要是 GC 相关的一些信息, 还有编译器, 类和实时编译的一些计算信息.

运用起来也很简略, 咱们只需知道当时Java进程号, 就能够了. 比方下面咱们有个Java进程号是44537, 然后输出它的GC计算信息:

 eric@supra ~ % jstat -gc 44537
    S0C         S1C         S0U         S1U          EC           EU           OC           OU          MC         MU       CCSC      CCSU     YGC     YGCT     FGC    FGCT     CGC    CGCT       GCT
    17920.0     21504.0     17441.5         0.0     180224.0      55871.7     229888.0      23506.0    42456.0    40736.2    5376.0    4833.1      4     0.032     2     0.052     -         -     0.084

能够看到, 它输出了GC相关的各种信息, 各个列的解说是:

S0C, S1C, EC, OC, MC, CCSC: 分别是Survivor0, Survivor1, Eden, Old, Metaspace, Compressed class space 的 Capacity, 即总容量的巨细.

S0U, S1U, EU, OU, MU, CCSU: 分别是Survivor0, Survivor1, Eden, Old, Metaspace, Compressed class space 的 Usage, 即已运用空间的巨细.

YGC, YGCT, FGC, FGCT, CGC, CGCT, GCT: 分别是Young GC(年青代GC)的次数和时刻, Full GC的次数和时刻, Concurrent GC的次数和时刻, 以及一切的GC所用的时刻.

下面是有关类的一些计算信息:

 eric@supra ~ % jstat -class  44537
Loaded  Bytes  Unloaded  Bytes     Time
  7749 15279.2        0     0.0       2.25

能够看到, JVM到当时共加载7749个类, 运用15279.2字节, 卸载0个类, 加载卸载类共花费2.25秒.

咱们上面的比方中, 都只是输出了一行, jstat 能够继续的每隔固定的时刻输出一次最新的计算信息, 比方下面的比方中, 咱们让它每隔5s输出一次, 共输出6次.

 eric@supra ~ % jstat -gcutil  44537 5s 6
  S0     S1     E      O      M     CCS    YGC     YGCT     FGC    FGCT     CGC    CGCT       GCT
 97.33   0.00  31.00  10.22  95.95  89.90      4     0.032     2     0.052     -         -     0.084
 97.33   0.00  31.00  10.22  95.95  89.90      4     0.032     2     0.052     -         -     0.084
 97.33   0.00  31.00  10.22  95.95  89.90      4     0.032     2     0.052     -         -     0.084
 97.33   0.00  31.00  10.22  95.95  89.90      4     0.032     2     0.052     -         -     0.084
 97.33   0.00  31.00  10.22  95.95  89.90      4     0.032     2     0.052     -         -     0.084
 97.33   0.00  31.00  10.22  95.95  89.90      4     0.032     2     0.052     -         -     0.084

能够看到, 进程号后面能够增加参数: 每隔多久输出一次, 单位能够是s(秒)或ms(毫秒), 共输出多少次.

jstat 这些计算信息能够方便的告知咱们当时JVM内部的一些计算信息, 方便咱们了解JVM内部当时的状况.

jcmd

JDK bin 目录下面这些指令当中, jcmd 是在确诊Java运用的过程中最有用的指令, 它不仅能检查当时JVM的发动参数, 体系特点, 还能发生 thread dump, heap dump, 发动停止JFR等.

下面, 咱们就以实践的方式来看看有哪些比较有用的功用.

输出 JVM 的一些内部信息

eric@supra ~ % jcmd 44537 VM.uptime
44537:
47227.855 s
eric@supra ~ % jcmd 44537 VM.version
44537:
OpenJDK 64-Bit Server VM version 25.362-b09
JDK 8.0_362
eric@supra ~ % jcmd 44537 VM.command_line
44537:
VM Arguments:
java_command: /Users/eric/graph/lib/nugrap-console-current.jar
java_class_path (initial): /Users/eric/graph/lib/nugrap-console-current.jar
Launcher Type: SUN_STANDARD
eric@supra ~ % jcmd 44537 VM.system_properties
44537:
#Sun Apr 16 20:38:49 PDT 2023
java.runtime.name=OpenJDK Runtime Environment
         ... 省略 ....
eric@supra ~ % jcmd 44537 VM.flags
44537:
-XX:CICompilerCount=12 -XX:InitialHeapSize=536870912 -XX:MaxHeapSize=8589934592 -XX:MaxNewSize=2863136768

上面的比方中, 咱们输出了以 VM. 最初的一些子指令, 它告知咱们当时JVM的一些相关信息, 如: 发动多久了, JVM的版别, 指令行, 体系特点, 一些 flags.

输出 thread dump

咱们能够经过 jstack 输出 thread dump, 也能够经过 jcmd <pid> Thread.print 来输出 thread dump.

eric@supra ~ % jcmd 44537 Thread.print
eric@supra ~ % jcmd 44537 Thread.print > /tmp/thread.txt

取得 heap dump

咱们能够经过 jmap 来取得 heap dump, 也能够用 jcmd <pid> GC.heap_dump 来发生 heap dump:

eric@supra ~ % jcmd 44537 GC.heap_dump /tmp/heap.hprof
44537:
Heap dump file created

手动触发 JVM 做 Full GC

eric@supra ~ % jcmd 44537 GC.run
44537:
Command executed successfully

打印加载了哪些类且每个类有多少实例

eric@supra ~ % jcmd 44537 GC.class_histogram

打印 ClassLoader 相关信息

eric@supra ~ % jcmd 44537 VM.classloader_stats

打印类计算信息

GC.class_stats 能具体的展现每个类有多少实例, 占用多少内存, 并且在元数据(Metaspace)区占用了多少内存, 关于确诊 Metaspace的内存走漏十分有用. 因为发生这些计算信息十分耗费体系资源, 对正常的运用影响较大, 尤其比较大的运用, 所以, 只需在运用发动时分, 增加 -XX:+UnlockDiagnosticVMOptions 发动选项, 才干执行这个指令.

eric@supra ~ % jcmd 44537 GC.class_stats
44537:
Index Super InstBytes KlassBytes annotations   CpAll MethodCount Bytecodes MethodAll   ROAll   RWAll    Total ClassName
    1    -1    711080        512           0       0           0         0         0      24     624      648 [B
    2    16    244424        680           0   22120         139      5679     41936   24632   42016    66648 java.lang.Class
    3    16    174960        624         128   14272         109      4576     50672   18640   48360    67000 java.lang.String
    4    16    126304        600           0    1368           9       213      2632    1488    3448     4936 java.util.concurrent.ConcurrentHashMap$Node
    5    -1    123080        512           0       0           0         0         0      24     624      648 [Ljava.lang.Object;
    6    16     95872        592           0    1392           7       149      1792    1152    2944     4096 java.util.HashMap$Node
    7    -1     67832        512           0       0           0         0         0      24     624      648 [C
    8    -1     47304        512           0       0           0         0         0      32     624      656 [Ljava.util.HashMap$Node;

检查 native 内存的运用情况

要检查 native 内存的运用, 必须在发动的时分增加发动参数 -XX:NativeMemoryTracking=summary-XX:NativeMemoryTracking=detail, 才干在发动后检查native 内存的运用情况, 这关于确诊非 heap 的内存走漏很有协助.

eric@supra ~ % jcmd 4823 VM.native_memory
4823:
Native Memory Tracking:
Total: reserved=10096545KB, committed=652509KB
-                 Java Heap (reserved=8388608KB, committed=524288KB)
                            (mmap: reserved=8388608KB, committed=524288KB)
-                     Class (reserved=1061162KB, committed=12970KB)
                            (classes #2009)
                            (  instance classes #1817, array classes #192)
                            (malloc=298KB #3511)
                            (mmap: reserved=1060864KB, committed=12672KB)
                            (  Metadata:   )
                            (    reserved=12288KB, committed=11264KB)
                            (    used=10912KB)
                            (    free=352KB)
                            (    waste=0KB =0.00%)
                            (  Class space:)
                            (    reserved=1048576KB, committed=1408KB)
                            (    used=1259KB)
                            (    free=149KB)
                            (    waste=0KB =0.00%)
              ... 省略 ...
-                    Thread (reserved=19540KB, committed=19540KB)
-                      Code (reserved=247907KB, committed=8223KB)
-                        GC (reserved=374483KB, committed=82643KB)
-                  Compiler (reserved=167KB, committed=167KB)
-                  Internal (reserved=625KB, committed=625KB)
-                     Other (reserved=10KB, committed=10KB)
-                    Symbol (reserved=3364KB, committed=3364KB)
-    Native Memory Tracking (reserved=344KB, committed=344KB)
-               Arena Chunk (reserved=200KB, committed=200KB)
-                   Logging (reserved=4KB, committed=4KB)
-                 Arguments (reserved=18KB, committed=18KB)
-                    Module (reserved=64KB, committed=64KB)
-              Synchronizer (reserved=41KB, committed=41KB)
-                 Safepoint (reserved=8KB, committed=8KB)

jcmd 还有一些其它子指令, 咱们这儿就不一一列举了, 经过 jcmd <pid> help 能够检查一切子指令.

总结

本文总结了日常的确诊过程中常用的一些 bin 目录里的小实用东西, 它们能帮咱们很快的了解JVM和运用自身的一些情况, 了解这些指令的运用以及能够输出的内容, 关于咱们确诊Java运用很有协助.

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。