第二十二章-JVM监控及诊断工具-命令行篇

一、概述

性能诊断是软件工程师在日常工作中需要经常面对和解决的问题,在用户体验至上的今天,解决好应用的性能问题能带来非常大的收益。

Java 作为最流行的编程语言之一,其应用性能诊断一直受到业界广泛关注。可能造成 Java 应用出现性能问题的因素非常多,例如线程控制、磁盘读写、数据库访问、网络I/O、垃圾收集等。想要定位这些问题,一款优秀的性能诊断工具必不可少。

1. 简单命令行工具

除了我们最了解的两个命令: javac,java 之外, 进入到安装jdk的bin目录,发现还有一系列辅助工具。这些辅助工具用来获取目标 JVM 不同方面、不同层次的信息,帮助开发人员很好地解决Java应用程序的一些疑难杂症。

mac系统jdk安装目录

win下JDK安装目录

官方源码地址:https://hg.openjdk.org/jdk/jdk11/file/1ddf9a99e4ad/src/jdk.jcmd/share/classes/sun/tools

二、jps:查看正在运行的Java进程

jps (Java Process Status):显示指定系统内所有的HotSpot虚拟机进程(查看虚拟机进程信息),可用于查询正在运行的虚拟机进程。

说明:对于本地虚拟机进程来说,进程的本地虚拟机ID与操作系统的进程ID是一致的,是唯一的。

基本使用语法为:jps [options] [hostid]

我们还可以通过追加参数,来打印额外的信息。

1. options参数

  • -q:仅仅显示LVMID(local virtual machine id),即本地虚拟机唯一id。不显示主类的名称等

  • -l:输出应用程序主类的全类名 或 如果进程执行的是jar包,则输出jar完整路径

  • -m:输出虚拟机进程启动时传递给主类main()的参数

  • -v:列出虚拟机进程启动时的JVM参数。比如:-Xms20m -Xmx50m是启动程序指定的jvm参数。

说明:以上参数可以综合使用。

补充:如果某 Java 进程关闭了默认开启的UsePerfData参数(即使用参数 -XX:-UsePerfData),那么jps命令(以及下面介绍的jstat)将无法探知该Java 进程。

解决Windows11下, jps 命令无效的问题: https://blog.csdn.net/lingyiwin/article/details/123238600

2. hostid参数

RMI注册表中注册的主机名。如果想要远程监控主机上的 java 程序,需要安装 jstatd

对于具有更严格的安全实践的网络场所而言,可能使用一个自定义的策略文件来显示对特定的可信主机或网络的访问,尽管这种技术容易受到IP地址欺诈攻击。

如果安全问题无法使用一个定制的策略文件来处理,那么最安全的操作是不运行jstatd服务器,而是在本地使用jstat和jps工具。

关于 jps [hostid] 的使用, 参考: https://www.cnblogs.com/keystone/p/10789382.html

3. 综合使用

jps -l -m 等价于 jps -lm

如何将信息输出到同级文件中:
语法:命令 > 文件名称
例如:jps -l > a.txt

三、jstat:查看JVM统计信息

jstat(JVM Statistics Monitoring Tool):用于监视虚拟机各种运行状态信息的命令行工具。它可以显示本地或者远程虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据。在没有GUI图形界面,只提供了纯文本控制台环境的服务器上,它将是运行期定位虚拟机性能问题的首选工具。常用于检测垃圾回收问题以及内存泄漏问题。

官方文档:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/jstat.html

基本使用语法为: jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]

查看命令相关参数:jstat -h 或 jstat -help

查看options选项: jstat -options

其中vmid是进程id号,也就是jps之后看到的前面的号码,如下:

image-20240814121104746

1. option参数

选项option可以由以下值构成。

类装载相关的:

  • -class:显示ClassLoader的相关信息:类的装载、卸载数量、总空间、类装载所消耗的时间等

垃圾回收相关的:

  • -gc:显示与GC相关的堆信息。包括Eden区、两个Survivor区、老年代、永久代等的容量、已用空间、GC时间合计等信息。

  • -gccapacity:显示内容与-gc基本相同,但输出主要关注Java堆各个区域使用到的最大、最小空间。

  • -gcutil:显示内容与-gc基本相同,但输出主要关注已使用空间占总空间的百分比。

  • -gccause:与-gcutil功能一样,但是会额外输出导致最后一次或当前正在发生的GC产生的原因。

  • -gcnew:显示新生代GC状况

  • -gcnewcapacity:显示内容与-gcnew基本相同,输出主要关注使用到的最大、最小空间

  • -geold:显示老年代GC状况

  • -gcoldcapacity:显示内容与-gcold基本相同,输出主要关注使用到的最大、最小空间

  • -gcpermcapacity:显示永久代使用到的最大、最小空间。

JIT相关的:

  • -compiler:显示JIT编译器编译过的方法、耗时等信息

  • -printcompilation:输出已经被JIT编译的方法

2. 使用示例

-class

jstat -class -t -h5 29544 1000 10

其中h5中的5代表每隔5个分隔一次,29544 代表类的进程id,1000代表每隔1000毫秒打印一次,10代表一共打印10次,如下所示:

-class

以下是输出结果中每个参数的含义:

  1. Timestamp: 运行时的时间戳,单位为秒,表示自 JVM 启动以来的时间。通过这个时间戳可以了解当前统计数据是在 JVM 运行多长时间之后获取的。在你的输出中,时间戳分别为 13.1, 14.1, 15.1 等。
  2. Loaded: 已加载的类的数量。这里显示的是自 JVM 启动以来,JVM 加载到内存中的类的总数。在输出中,Loaded 的值始终为 610,表示在每个采样点 JVM 中已加载了 610 个类。
  3. Bytes: 已加载的类所占用的内存空间大小,单位为 KB。在输出中,Bytes 的值始终为 1234.0 KB,表示 JVM 中已加载的类占用了 1234 KB 的内存空间。
  4. Unloaded: 自 JVM 启动以来,已卸载的类的数量。在你的输出中,Unloaded 的值始终为 0,表示没有卸载过任何类。
  5. Bytes: 已卸载的类释放的内存空间大小,单位为 KB。因为没有类被卸载,所以这个值始终为 0.0 KB。
  6. Time: JVM 在类加载和卸载上所花费的总时间,单位为秒。这里的 Time 值为 0.05 秒,表示 JVM 在处理类加载和卸载操作上总共花费了 0.05 秒。

-gc

jstat -gc -t -h5 28100 1000 10

其中h5中的5代表每隔5个分隔一次,28100 代表类的进程id,1000代表每隔1000毫秒打印一次,10代表一共打印10次,如下所示:

-gc

以下是每个参数的含义和解释:

  1. Timestamp: 运行时的时间戳,单位为秒,表示自 JVM 启动以来的时间。在你的输出中,时间戳分别为 12.7, 13.7, 14.7 等。
  2. S0C (Survivor Space 0 Capacity): 新生代中第一个存活区(Survivor 0 区)的容量,单位为 KB。在你的输出中,S0C 的值始终为 2048.0 KB,表示 Survivor 0 区的容量为 2048 KB。
  3. S1C (Survivor Space 1 Capacity): 新生代中第二个存活区(Survivor 1 区)的容量,单位为 KB。S1C 的值也为 2048.0 KB,表示 Survivor 1 区的容量为 2048 KB。
  4. S0U (Survivor Space 0 Utilization): 新生代中第一个存活区(Survivor 0 区)的使用量,单位为 KB。在输出中,S0U 的值大部分时间为 0.0,表示 Survivor 0 区未被使用。
  5. S1U (Survivor Space 1 Utilization): 新生代中第二个存活区(Survivor 1 区)的使用量,单位为 KB。在第一个时间戳中,S1U 的值为 0.0,表示未使用。在后续时间戳中,S1U 的值为 2024.3 KB,表示 Survivor 1 区几乎已满。
  6. EC (Eden Space Capacity): Eden 区的容量,单位为 KB。EC 的值为 16384.0 KB,表示 Eden 区的容量为 16384 KB。
  7. EU (Eden Space Utilization): Eden 区的使用量,单位为 KB。随着时间的推移,EU 的值从 13888.0 KB 增加到 5002.0 KB,表示 Eden 区的使用情况逐渐变化。
  8. OC (Old Generation Capacity): 老年代的容量,单位为 KB。OC 的值为 40960.0 KB,表示老年代的容量为 40960 KB。
  9. OU (Old Generation Utilization): 老年代的使用量,单位为 KB。最初 OU 的值为 0.0 KB,但在时间戳 15.7 之后变为 11817.8 KB,表示部分对象被移入了老年代。
  10. MC (Metaspace Capacity): 元空间的容量,单位为 KB。MC 的值为 4480.0 KB,后续增加到 4864.0 KB,表示元空间的容量略有增加。
  11. MU (Metaspace Utilization): 元空间的使用量,单位为 KB。MU 的值从 776.8 KB 增加到 3743.2 KB,表示元空间的使用情况变化较大。
  12. CCSC (Compressed Class Space Capacity): 压缩类空间的容量,单位为 KB。CCSC 的值最初为 384.0 KB,随后增加到 512.0 KB,表示压缩类空间的容量有所增加。
  13. CCSU (Compressed Class Space Utilization): 压缩类空间的使用量,单位为 KB。CCSU 的值从 76.6 KB 增加到 409.7 KB,表示压缩类空间的使用情况变化较大。
  14. YGC (Young Generation GC Events): 新生代 GC(Minor GC)的次数。YGC 的值最初为 0,随后增加到 1,表示发生了一次 Minor GC。
  15. YGCT (Young Generation GC Time): 新生代 GC 总时间,单位为秒。YGCT 的值从 0.000 秒增加到 0.007 秒,表示 Minor GC 总共花费了 0.007 秒。
  16. FGC (Full GC Events): Full GC(涉及整个堆的 GC) 的次数。在输出中,FGC 的值为 0,表示未发生过 Full GC。
  17. FGCT (Full GC Time): Full GC 总时间,单位为秒。FGCT 的值为 0.000 秒,表示 Full GC 总共花费了 0 秒。
  18. GCT (Total GC Time): GC 总时间,单位为秒,包含 Minor GC 和 Full GC 的时间。GCT 的值从 0.000 秒增加到 0.007 秒,表示 GC 总时间为 0.007 秒。

-gccapacity

jstat -gccapacity 3060,其中 3060 代表类的进程id,执行结果如下:

-gccapacity

以下是每个参数的含义:

  1. NGCMN (Minimum New Generation Capacity): 新生代内存区域的最小容量。
  2. NGCMX (Maximum New Generation Capacity): 新生代内存区域的最大容量。
  3. NGC (Current New Generation Capacity): 新生代内存区域的当前容量。
  4. S0C (Current Survivor Space 0 Capacity): 新生代中第一个存活区的当前容量。
  5. S1C (Current Survivor Space 1 Capacity): 新生代中第二个存活区的当前容量。
  6. EC (Current Eden Space Capacity): Eden 区的当前容量。Eden 区是新对象首先分配内存的地方。
  7. OGCMN (Minimum Old Generation Capacity): 老年代内存区域的最小容量。
  8. OGCMX (Maximum Old Generation Capacity): 老年代内存区域的最大容量。
  9. OGC (Current Old Generation Capacity): 老年代内存区域的当前容量。
  10. OC (Current Old Space Capacity): 老年代的当前容量,通常等同于 OGC。
  11. MCMN (Minimum Metaspace Capacity): 元空间的最小容量(元空间用于存储类元数据)。
  12. MCMX (Maximum Metaspace Capacity): 元空间的最大容量。
  13. MC (Current Metaspace Capacity): 元空间的当前容量。
  14. CCSMN (Minimum Compressed Class Space Capacity): 压缩类空间的最小容量。压缩类空间用于存储类的相关元数据(如果开启了类元数据的压缩)。
  15. CCSMX (Maximum Compressed Class Space Capacity): 压缩类空间的最大容量。
  16. CCSC (Current Compressed Class Space Capacity): 压缩类空间的当前容量。
  17. YGC (Number of Young Generation GC Events): 新生代 GC(通常称为 Minor GC)的次数。
  18. FGC (Number of Full GC Events): Full GC(涉及整个堆的 GC) 的次数。

-gcutil

jstat -gcutil 33520,其中13152代表类的进程id,执行结果如下所示:

-gcutil

以下是每个参数的含义:

  1. S0 (Survivor Space 0 Utilization): 新生代中第一个存活区(Survivor 0 区)的使用率(百分比)。在这个例子中,S0 的值为 0.00,表示 Survivor 0 区未被使用。
  2. S1 (Survivor Space 1 Utilization): 新生代中第二个存活区(Survivor 1 区)的使用率(百分比)。S1 的值为 0.00,表示 Survivor 1 区未被使用。
  3. E (Eden Space Utilization): Eden 区的使用率(百分比)。E 的值为 81.10,表示 Eden 区的 81.10% 被使用。
  4. O (Old Generation Utilization): 老年代的使用率(百分比)。O 的值为 0.00,表示老年代未被使用。
  5. M (Metaspace Utilization): 元空间的使用率(百分比)。M 的值为 17.34,表示元空间的 17.34% 被使用。
  6. CCS (Compressed Class Space Utilization): 压缩类空间的使用率(百分比)。CCS 的值为 19.94,表示压缩类空间的 19.94% 被使用。
  7. YGC (Number of Young Generation GC Events): 新生代 GC(Minor GC)的次数。YGC 的值为 0,表示未发生过 Minor GC。
  8. YGCT (Young Generation GC Time): 新生代 GC 总时间(以秒为单位)。YGCT 的值为 0.000,表示 Minor GC 消耗的总时间为 0 秒。
  9. FGC (Number of Full GC Events): Full GC(涉及整个堆的 GC) 的次数。FGC 的值为 0,表示未发生过 Full GC。
  10. FGCT (Full GC Time): Full GC 总时间(以秒为单位)。FGCT 的值为 0.000,表示 Full GC 消耗的总时间为 0 秒。
  11. GCT (Total GC Time): GC 总时间(以秒为单位),包括所有 Minor GC 和 Full GC 的时间。GCT 的值为 0.000,表示 GC 总时间为 0 秒。

-gccause

jstat -gccause 9180,其中 9180 代表类的进程id,执行结果如下:

-gccause

以下是每个参数的含义:

  1. S0 (Survivor Space 0 Utilization): 新生代中第一个存活区(Survivor 0 区)的使用率(百分比)。在这个例子中,S0 的值为 0.00,表示 Survivor 0 区未被使用。
  2. S1 (Survivor Space 1 Utilization): 新生代中第二个存活区(Survivor 1 区)的使用率(百分比)。S1 的值为 0.00,表示 Survivor 1 区未被使用。
  3. E (Eden Space Utilization): Eden 区的使用率(百分比)。E 的值为 66.45,表示 Eden 区的 66.45% 被使用。
  4. O (Old Generation Utilization): 老年代的使用率(百分比)。O 的值为 0.00,表示老年代未被使用。
  5. M (Metaspace Utilization): 元空间的使用率(百分比)。M 的值为 17.34,表示元空间的 17.34% 被使用。
  6. CCS (Compressed Class Space Utilization): 压缩类空间的使用率(百分比)。CCS 的值为 19.94,表示压缩类空间的 19.94% 被使用。
  7. YGC (Number of Young Generation GC Events): 新生代 GC(Minor GC)的次数。YGC 的值为 0,表示未发生过 Minor GC。
  8. YGCT (Young Generation GC Time): 新生代 GC 总时间(以秒为单位)。YGCT 的值为 0.000,表示 Minor GC 消耗的总时间为 0 秒。
  9. FGC (Number of Full GC Events): Full GC(涉及整个堆的 GC) 的次数。FGC 的值为 0,表示未发生过 Full GC。
  10. FGCT (Full GC Time): Full GC 总时间(以秒为单位)。FGCT 的值为 0.000,表示 Full GC 消耗的总时间为 0 秒。
  11. GCT (Total GC Time): GC 总时间(以秒为单位),包括所有 Minor GC 和 Full GC 的时间。GCT 的值为 0.000,表示 GC 总时间为 0 秒。
  12. LGCC (Last GC Cause): 最近一次 GC 发生的原因。LGCC 的值为 Allocation Failure,表示最近一次 GC 是由于内存分配失败(即 Eden 空间不足以容纳新对象)而触发的。
  13. GCC (Current GC Cause): 当前 GC 发生的原因。如果当前正在进行 GC,该值会显示触发 GC 的原因。GCC 的值为 No GC,表示当前没有 GC 正在进行。

2. interval参数

用于指定输出统计数据的周期,单位为毫秒。即:查询间隔

3. count参数

用于指定查询的总次数

4. -t参数

可以在输出信息前加上一个Timestamp列,显示程序的运行时间。单位:秒

5. -h参数

可以在周期性数据输出时,输出多少行数据后输出一个表头信息

补充

jstat还可以用来判断是否出现内存泄漏。

第1步:在长时间运行的 Java 程序中,我们可以运行jstat命令连续获取多行性能数据,并取这几行数据中 OU 列(即已占用的老年代内存)的最小值。

第2步:然后,我们每隔一段较长的时间重复一次上述操作,来获得多组 OU 最小值。如果这些值呈上涨趋势,则说明该 Java 程序的老年代内存已使用量在不断上涨,这意味着无法回收的对象在不断增加,因此很有可能存在内存泄漏。

四、jinfo:实时查看和修改JVM配置参数

jinfo(Configuration Info for Java):查看虚拟机配置参数信息,也可用于调整虚拟机的配置参数。在很多情况下,Java应用程序不会指定所有的Java虚拟机参数。而此时,开发人员可能不知道某一个具体的Java虚拟机参数的默认值。在这种情况下,可能需要通过查找文档获取某个参数的默认值。这个查找过程可能是非常艰难的。但有了jinfo工具,开发人员可以很方便地找到Java虚拟机参数的当前值。

基本使用语法为:jinfo [options] pid

说明:java 进程ID必须要加上

选项 选项说明
no option 输出全部的参数和系统属性
-flag name 输出对应名称的参数
-flag [+-]name 开启或者关闭对应名称的参数 只有被标记为manageable的参数才可以被动态修改
-flag name=value 设定对应名称的参数
-flags 输出全部的参数
-sysprops 输出系统属性

官方帮助文档: https://docs.oracle.com/en/java/javase/11/tools/jinfo.html

1. jinfo -sysprops pid 输出系统属性

jinfo -sysprops 2176

2176 是通过 jps 指令查询的 java 进程ID, jinfo -sysprops pid 可以查看由 System.getProperties() 取得的参数

-sysprops

2. jinfo -flags pid 输出全部的参数

jinfo -flags 25244

25244 是通过 jps 指令查询的 java 进程ID, jinfo -flags pid 可以查看当前进程JVM赋了值的参数信息.

-flags

3. jinfo -flag 参数 pid 查看某个java进程的具体参数信息

jinfo -flag UseG1GC 24340

其中: UseG1GC 是要查看的具体的参数, 24340 是通过 jps 指令查询的 java 进程ID

-flag param

4. jinfo -flag [+-]name pid 修改参数信息

jinfo不仅可以查看运行时某一个]ava虚拟机参数的实际取值,甚至可以在运行时修改部分参数,并使之立即生效。

但是,并非所有参数都支持动态修改。参数只有被标记为 manageable 的 flag 可以被实时修改。其实,这个修改能力是极其有限的。

4.1 查看被标记为manageable的参数

root@ubuntu:~# java -XX:+PrintFlagsFinal -version | grep manageable
     intx CMSAbortablePrecleanWaitMillis            = 100                                 {manageable}
     intx CMSTriggerInterval                        = -1                                  {manageable}
     intx CMSWaitDuration                           = 2000                                {manageable}
     bool HeapDumpAfterFullGC                       = false                               {manageable}
     bool HeapDumpBeforeFullGC                      = false                               {manageable}
     bool HeapDumpOnOutOfMemoryError                = false                               {manageable}
    ccstr HeapDumpPath                              =                                     {manageable}
    uintx MaxHeapFreeRatio                          = 100                                 {manageable}
    uintx MinHeapFreeRatio                          = 0                                   {manageable}
     bool PrintClassHistogram                       = false                               {manageable}
     bool PrintClassHistogramAfterFullGC            = false                               {manageable}
     bool PrintClassHistogramBeforeFullGC           = false                               {manageable}
     bool PrintConcurrentLocks                      = false                               {manageable}
     bool PrintGC                                   = false                               {manageable}
     bool PrintGCDateStamps                         = false                               {manageable}
     bool PrintGCDetails                            = false                               {manageable}
     bool PrintGCID                                 = false                               {manageable}
     bool PrintGCTimeStamps                         = false                               {manageable}
java version "1.8.0_261"
Java(TM) SE Runtime Environment (build 1.8.0_261-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.261-b12, mixed mode)

4.2 针对boolean类型的修改

jinfo -flag [+|-]参数名称 进程id

PID可以通过jps命令查看,如果使用+号,那就可以让该参数起作用,否则使用-号就让该参数不起作用,具体例子如下:

-flag boolean类型

PrintGCDetails 参数用于控制垃圾回收 (GC) 详情的输出

4.3 针对非boolean类型的修改

jinfo -flag 参数名称=参数值 进程id

PID可以通过jps命令查看,如果使用+号,那就可以让该参数起作用,否则使用-号就让该参数不起作用,具体例子如下:

-flag 非boolean类型

MaxHeapFreeRatio 定义了堆内存中空闲内存的最大百分比。当堆内存的空闲比例超过此值时,JVM可能会考虑缩小堆的大小。默认值通常为70%,意味着当超过70%的堆空闲时,JVM会尝试减小堆大小。(当-Xmx与-Xms相等时,该配置无效)

5. 拓展

  • java -XX:+PrintFlagsInitial 查看所有JVM参数启动的初始值

    -XX:+PrintFlagsInitial

    在编译后的class文件夹下执行

  • java -XX:+PrintFlagsFinal 查看所有JVM参数的最终值

    • java -XX:+UseSerialGC -XX:+PrintFlagsFinal com.atguigu.jstat.GCTest
      • 这个命令会启动 JVM,并使用 Serial GC 进行垃圾回收,同时打印出所有 JVM 参数的最终配置。然后 JVM 会执行 com.atguigu.jstat.GCTest 类中的 main 方法。

    -XX:+PrintFlagsFinal

    在编译后的class文件夹下执行

  • java -XX:+PrintCommandLineFlags 查看那些已经被用户或者JVM设置过的详细的XX参数的名称和值

    • java -XX:+UseSerialGC -XX:+PrintCommandLineFlags com.atguigu.jstat.GCTest

      • 这个命令启动 JVM,并使用 Serial 垃圾回收器,同时在启动时打印出所有命令行标志。随后,JVM 会执行 com.atguigu.jstat.GCTest 类中的 main 方法。

      +PrintCommandLineFlags

      在编译后的class文件夹下执行

五、jmap:导出内存映像文件&内存使用情况

1. 简述

jmap(JVM Memory Map):作用一方面是获取dump文件(堆转储快照文件,二进制文件),它还可以获取目标Java进程的内存相关信息,包括Java堆各区域的使用情况、堆中对象的统计信息、类加载信息等。开发人员可以在控制台中输入命令“ jmap -help ”查阅 jmap 工具的具体使用方式和一些标准选项配置。

2. 基本语法

  • -dump
    • 生成Java堆转储快照:dump文件
  • -dump:live
    • 生成Java堆转储快照:dump文件, 但只保存堆中的存活对象
    • 相较于 -dump , 文件体积会小一些
  • -heap
    • 输出整个堆空间的详细信息,包括GC的使用、堆配置信息,以及内存的使用信息等
  • -histo
    • 输出堆中对象的同级信息,包括类、实例数量和合计容量
    • 特别的:-histo:live 只统计堆中的存活对象
  • -histo:live
    • 输出堆中对象的同级信息,包括类、实例数量和合计容量, 但是只统计堆中的存活对象
  • -permstat
    • 以ClassLoader 为统计口径输出永久代的内存状态信息
    • 仅 linux/solaris 平台有效
  • -finalizerinfo
    • 显示在 F-Queue 中等待 Finalizer 线程执行 finalize 方法的对象
    • 仅linux/solaris平台有效
  • -F
    • 当虚拟机进程对-dump选项没有任何响应时,可使用此选项强制执行生成dump文件
    • 仅linux/solaris平台有效
  • -h | -help
    • jamp工具使用的帮助命令
  • -J
    • 传递参数给 jmap 启动的 jvm

3. 导出内存映像文件

3.1 手动导出

jmap -dump:format=b,file=<filename.hprof> <pid>

说明:

  • <filename.hprof> 中的filename是文件名称,而 .hprof 是后缀名,<***>代表该值可以省略<>,当然后面的 是进程id,需要通过jps查询出来
  • format=b 表示生成的是标准的dump文件,用来进行格式限定

具体例子如下:

  • 生成堆中所有对象的快照:

    jmap -dump:format=b,file=d:\1.hprof 22096

-dump

  • 生成堆中存活对象的快照:

    jmap -dump:live,format=b,file=d:\2.hprof 22012

    -dump:live

其中 file= 后面的是生成的dump文件地址,最后的11696 是进程id,可以通过jps查看

导出的dump文件

一般使用的是第二种方式,也就是生成堆中存活对象的快照,毕竟这种方式生成的dump文件更小,我们传输处理都更方便

由于jmap将访问堆中的所有对象,为了保证在此过程中不被应用线程干扰,jmap需要借助安全点机制,让所有线程停留在不改变堆中数据的状态。也就是说,由jmap导出的堆快照必定是安全点位置的。这可能导致基于该堆快照的分析结果存在偏差。
举个例子,假设在编译生成的机器码中,某些对象的生命周期在两个安全点之间,那么:1ive选项将无法探知到这些对象。
另外,如果某个线程长时间无法跑到安全点,jmap将一直等下去。与前面讲的jstat则不同垃圾回收器会主动将jstat所需要的摘要数据保存至固定位置之中,而jstat只需直接读取即可。

3.2 自动导出

当程序发生OOM退出系统时,一些瞬时信息都随着程序的终止而消失,而重现OOM问题往往比较困难或者耗时。此时若能在OOM时,自动导出dump文件就显得非常迫切。

这里介绍一种比较常用的取得堆快照文件的方法,即使用:

  • -XX:+HeapDumpOn0utOfMemoryError : 在程序发生OOM时,导出应用程序的当前堆快照。
  • -XX:HeapDumpPath : 可以指定堆快照的保存位置。

例如:

-Xms100m -Xmx100m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:\m.hprof

VM Options 配置

示例代码:

public class GCTest {
    public static void main(String[] args) {
        ArrayList<byte[]> list = new ArrayList<>();

        for (int i = 0; i < 1000; i++) {
            byte[] arr = new byte[1024 * 100];//100KB
            list.add(arr);
            try {
                Thread.sleep(60);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

运行代码,当程序出现OOM时, 会自动在D盘根目录下生成 m.hprof 文件

image-20240816083747704

4. 显示堆内存相关信息

4.1 jmap -heap 进程id

  • jmap -heap <pid> 只是时间点上的堆信息,而 jstat 后面可以添加参数,可以指定时间动态观察数据改变情况,而图形化界面工具,例如 jvisualvm 等,它们可以用图表的方式动态展示出相关信息,更加直观明了

    jmap -heap

**参数解析: **

  • 一般信息

    Attaching to process ID 28420, please wait...
    Debugger attached successfully.
    Server compiler detected.
    JVM version is 25.241-b07
    • Attaching to process ID 28420: 表示 jmap 工具正在连接到进程 ID 为 28420 的 JVM 实例。

    • Debugger attached successfully: 调试器成功附加到 JVM 实例。

    • Server compiler detected: JVM 使用的是服务器模式的编译器,这通常用于高性能环境。

    • JVM version is 25.241-b07: 表示 JVM 的版本号是 25.241-b07,这通常是 Java 8 的一个版本。

  • GC 信息

    using thread-local object allocation.
    Parallel GC with 15 thread(s)
    • using thread-local object allocation: JVM 使用线程本地对象分配(TLOA),这是为了提高对象分配的效率。

    • Parallel GC with 15 thread(s): JVM 使用的是并行垃圾回收器(Parallel GC),并且使用了 15 个线程来进行垃圾回收。

  • 堆内存配置 (Heap Configuration)

    Heap Configuration:
       MinHeapFreeRatio         = 0
       MaxHeapFreeRatio         = 100
       MaxHeapSize              = 104857600 (100.0MB)
       NewSize                  = 34603008 (33.0MB)
       MaxNewSize               = 34603008 (33.0MB)
       OldSize                  = 70254592 (67.0MB)
       NewRatio                 = 2
       SurvivorRatio            = 8
       MetaspaceSize            = 21807104 (20.796875MB)
       CompressedClassSpaceSize = 1073741824 (1024.0MB)
       MaxMetaspaceSize         = 17592186044415 MB
       G1HeapRegionSize         = 0 (0.0MB)
    • MinHeapFreeRatio = 0: 最小堆内存空闲比例为 0%,表示没有保留的最小空闲空间。

    • MaxHeapFreeRatio = 100: 最大堆内存空闲比例为 100%,表示堆可以完全空闲。

    • MaxHeapSize = 104857600 (100.0MB): 最大堆大小设置为 100MB。

    • NewSize = 34603008 (33.0MB): 新生代的初始大小为 33MB。

    • MaxNewSize = 34603008 (33.0MB): 新生代的最大大小为 33MB。

    • OldSize = 70254592 (67.0MB): 老年代的初始大小为 67MB。

    • NewRatio = 2: 新生代与老年代的比例为 1:2。

    • SurvivorRatio = 8: Eden 区与 Survivor 区的比例为 8:1。

    • MetaspaceSize = 21807104 (20.796875MB): 元空间的初始大小为约 20.8MB。

    • CompressedClassSpaceSize = 1073741824 (1024.0MB): 压缩类空间大小为 1024MB。

    • MaxMetaspaceSize = 17592186044415 MB: 最大元空间大小非常大,几乎没有限制。

    • G1HeapRegionSize = 0 (0.0MB): G1 GC 堆区域大小为 0,表示未使用 G1 垃圾回收器。

  • 堆内存使用情况 (Heap Usage)

    Heap Usage:
    PS Young Generation
    Eden Space:
       capacity = 26214400 (25.0MB)
       used     = 23077464 (22.008384704589844MB)
       free     = 3136936 (2.9916152954101562MB)
       88.03353881835938% used
    From Space:
       capacity = 4194304 (4.0MB)
       used     = 0 (0.0MB)
       free     = 4194304 (4.0MB)
       0.0% used
    To Space:
       capacity = 4194304 (4.0MB)
       used     = 0 (0.0MB)
       free     = 4194304 (4.0MB)
       0.0% used
    PS Old Generation
       capacity = 70254592 (67.0MB)
       used     = 0 (0.0MB)
       free     = 70254592 (67.0MB)
       0.0% used

    PS Young Generation: 年轻代(Parallel Scavenge GC)信息。

    • Eden Space
      • capacity = 26214400 (25.0MB): Eden 区的总容量为 25MB。
      • used = 23077464 (22.008384704589844MB): 已使用 22MB。
      • free = 3136936 (2.9916152954101562MB): 空闲 3MB。
      • 88.03353881835938% used: 使用率为 88%。
    • From Space
      • capacity = 4194304 (4.0MB): From Survivor 区的总容量为 4MB。
      • used = 0 (0.0MB): 未使用。
      • free = 4194304 (4.0MB): 完全空闲。
      • 0.0% used: 使用率为 0%。
    • To Space
      • capacity = 4194304 (4.0MB): To Survivor 区的总容量为 4MB。
      • used = 0 (0.0MB): 未使用。
      • free = 4194304 (4.0MB): 完全空闲。
      • 0.0% used: 使用率为 0%。

    PS Old Generation: 老年代信息。

    • capacity = 70254592 (67.0MB): 老年代的总容量为 67MB。

    • used = 0 (0.0MB): 未使用。

    • free = 70254592 (67.0MB): 完全空闲。

    • 0.0% used: 使用率为 0%。

  • 字符串池的使用情况

    2164 interned Strings occupying 200768 bytes.
    • 2164 interned Strings: 字符串池中有 2164 个字符串。

    • occupying 200768 bytes: 这些字符串占用了约 200768 字节(约 196 KB)的内存。

4.2 jmap -histo 进程id

输出堆中对象的同级信息,包括类、实例数量和合计容量,也是这一时刻的内存中的对象信息

jmap -histo

5. 其它作用

  • jmap -permstat 进程id : 查看系统的ClassLoader信息
  • jmap -finalizerinfo : 查看堆积在finalizer队列中的对象

这两个指令仅linux/solaris平台有效,所以无法在windows操作平台上使用

6. 总结

  • 由于jmap将访问堆中的所有对象,为了保证在此过程中不被应用线程干扰,jmap需要借助安全点机制,让所有线程停留在不改变堆中数据的状态。也就是说,由jmap导出的堆快照必定是安全点位置的。这可能导致基于该堆快照的分析结果存在偏差。
  • 举个例子,假设在编译生成的机器码中,某些对象的生命周期在两个安全点之间,那么 :live 选项将无法探知到这些对象。
  • 另外,如果某个线程长时间无法跑到安全点,jmap将一直等下去。与前面讲的jstat则不同, 垃圾回收器会主动将jstat所需要的摘要数据保存至固定位置之中,而jstat只需直接读取即可。

六、jhat:JDK自带堆分析工具

jhat 命令在 jdk9 及其之后就被移除了,官方建议使用 Jvisualvm 代替jhat,所以该指令只需简单了解一下即可

jhat (JVM Heap Analysis Tool):

  • Sun ]DK 提供的 jhat 命令与 jmap 命令搭配使用,用于分析 jmap 生成的 heap dump文件(堆转储快照) 。jhat 内置了一个微型的 HTTP/HTML 服务器, 生成 dump 文件的分析结果后, 用户可以在浏览器中查看分析结果(分析虚拟机转储快照信息)。
  • 使用了 jhat 命令,就启动了一个http服务,端口是 7008,即 http://1ocalhost:7800/,就可以在浏览器里分析。
  • 说明: jhat 命令在JDK9、JDK18中已经被删除,官方建议用 VisualVM 代替

1. 基本语法

jhat [option] [dumpfile]

其中dumpfile代表dump文件的地址以及名称,例如:

jhat

jhat web

2. options参数

  • -stack false l true
    • 关闭|打开对象分配调用栈跟踪
  • -refs false l true
    • 关闭|打开对象引用跟踪
  • -port port-number
    • 设置 jhat HTTP Server的端口号,默认7000
  • -exclude exclude-file
    • 执行对象查询时需要排除的数据成员
  • -baseline exclude-file
    • 指定一个基准堆转储
  • -debug int
    • 设置debug级别
  • -version
    • 启动后显示版本信息就退出
  • -J
    • 传入启动参数,比如小 -J -Xmx512m

示例:

jhat -stack false d:\1.hprof

jhat -port 6565 d:\1.hprof

jhat -version

七、jstack:打印JVM中线程快照

1. 基本情况

jstack (JVM stack Trace) : 用于生成虚拟机指定进程当前时刻的线程快照(虚拟机堆栈跟踪)。 线程快照就是当前虚拟机内指定进程的每一条线程正在执行的方法堆栈的集合。

生成线程快照的作用 : 可用于定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等问题。这些都是导致线程长时间停顿的常见原因。当线程出现停顿时,就可以用 jstack 显示各个线程调用的堆情况。

官方帮助文档:
https://docs.oracle.com/en/java/javase/11/tools/jstack.html

thread dump 中,要留意下面几种状态

  • 死锁,Deadlock(重点关注)
  • 等待资源,Waiting on condition(重点关注)
  • 等待获取监视器,Waiting on monitor entry(重点关注)
  • 阻塞,Blocked(重点关注)
  • 执行中,Runnable
  • 暂停,Suspended
  • 对象等待中,Object.wait() 或 TIMED WAITING
  • 停止,Parked

2. 基本语法

jstack

它的基本使用语法为: jstack option pid

option 参数

  • -F

    当正常输出的请求不被响应时,强制输出线程堆栈

  • -l

    除堆栈外,显示关于锁的附加信息

  • -m

    如果调用本地方法的话,可以显示C/C++的堆栈

  • -h

    帮助操作

使用示例: 测试jstack排查死锁

  • 测试代码

    public class ThreadDeadLock {
    
        public static void main(String[] args) {
    
            StringBuilder s1 = new StringBuilder();
            StringBuilder s2 = new StringBuilder();
    
            new Thread(){
                @Override
                public void run() {
    
                    synchronized (s1){
    
                        s1.append("a");
                        s2.append("1");
    
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
    
                        synchronized (s2){
                            s1.append("b");
                            s2.append("2");
    
                            System.out.println(s1);
                            System.out.println(s2);
                        }
    
                    }
    
                }
            }.start();
    
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (s2){
    
                        s1.append("c");
                        s2.append("3");
    
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
    
                        synchronized (s1){
                            s1.append("d");
                            s2.append("4");
    
                            System.out.println(s1);
                            System.out.println(s2);
                        }
                    }
                }
            }).start();
    
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    Map<Thread, StackTraceElement[]> all = Thread.getAllStackTraces();//追踪当前进程中的所有的线程
                    Set<Map.Entry<Thread, StackTraceElement[]>> entries = all.entrySet();
                    for(Map.Entry<Thread, StackTraceElement[]> en : entries){
                        Thread t = en.getKey();
                        StackTraceElement[] v = en.getValue();
                        System.out.println("【Thread name is :" + t.getName() + "】");
                        for(StackTraceElement s : v){
                            System.out.println("\t" + s.toString());
                        }
                    }
                }
            }).start();
        }
    
    }
  • 启动程序

  • 使用 jstack 打印线程信息

    jstack -l 19076 : 其中 19076 是程序的进程ID

    jstack 排查死锁问题

八、jcmd:多功能命令行

在 JDK 1.7 以后,新增了一个命令行工具jcmd。

它是一个多功能的工具,可以用来实现前面除了 jstat 之外所有命令的功能。比如:用它来导出堆、内存使用、查看Java进程、导出线程信息、执行GC、JVM运行时间等

官方帮助文档: https://docs.oracle.com/en/java/javase/11/tools/jcmd.html

jcmd 拥有 jmap 的大部分功能,并且在Oracle的官方网站上也推荐使用 jcmd 命令代 jmap 命令

1. 基本语法

  • jcmd -l

    列出所有的JVM进程

  • jcmd 进程号 help

    针对指定的进程,列出支持的所有具体命令

    jcmd help

    根据以上命令来替换之前的那些操作:

    • Thread.print 可以替换 jstack 指令
    • GC.class_histogram 可以替换 jmap中的 -histo 操作
    • GC.heap_dump 可以替换 jmap中的 -dump 操作
    • GC.run 可以查看GC的执行情况
    • VM.uptime 可以查看程序的总执行时间,可以替换jstat指令中的 -t 操作
    • VM.system_properties 可以替换 jinfo -sysprops 进程id
    • VM.flags 可以获取JVM的配置参数信息
  • jcmd 进程号 具体命令

    显示指定进程的指令命令的数据

    jcmd 打印线程信息

九、jstatd:远程主机信息收集

之前的指令只涉及到监控本机的Java应用程序,而在这些工具中,一些监控工具也支持对远程计算机的监控(如 jpsjstat )。为了启用远程监控,则需要配合使用 jstatd 工具。
命令 jstatd 是一个RMI服务端程序,它的作用相当于代理服务器,建立本地计算机与远程监控工具的通信。jstatd 服务器将本机的Java应用程序信息传递到远程计算机。

jstatd的理解


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 george_95@126.com