JDK 的 bin 目录下有很多小工具,除了编译和运行 Java 程序外,打包、部署、调试、监控、运维等各个场景都可能用到它们。官方文档:https://docs.oracle.com/en/java/javase/11/tools/main-tools-create-and-build-applications.html
image.png
下面我们介绍这些工具中的一部分,主要是用于监视虚拟机运行状态和进行故障处理的工具。

jps

jps(JVM Process Status Tool)的功能和 UNIX 的 ps 命令类似:列出正在运行的虚拟机进程,并显示虚拟机执行主类(main 函数所在的类)的名称及对应进程的本地虚拟机唯一 ID(LVMID)。jps还可以通过 RMI 协议查询开启了 RMI 服务的远程虚拟机进程状态,参数 hostid 为 RMI 注册表中注册的主机名。

在默认情况下,jps 的输出信息包括 Java 进程的进程 ID 以及主类名。我们还可以通过追加参数来打印额外的信息。具体如下所示:

jps 的命令格式:

  1. jps [options] [hostid]
  • options:可选性主要有以下几个 | 选项 | 作用 | | —- | —- | | -m | 输出虚拟机进程启动时传递给主类 main() 函数的参数 | | -l | 输出主类的全名,如果进程执行的是 JAR 包,则输出 JAR 包路径 | | -v | 输出虚拟机进程启动时的 JVM 参数 |

jps 的执行样例:
image.png
需要注意的是,如果某 Java 进程关闭了默认开启的 UsePerfData 参数,那么 jps 命令(以及下面介绍的 jstat)将无法探知该 Java 进程。

jstat

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

jstat 的命令格式:

  1. jstat [option vmid [interval[s|ms] [count]]]
  • vmid:如果是本地虚拟机进程,VMID 与 LVMID 是一致的
  • interval:查询间隔
  • count:查询次数
  • option:代表用户希望查询的虚拟机信息,主要分为:类加载、垃圾收集、运行期编译状况


选项 作用
-class 监视类加载、卸载数量、总空间以及类装载所耗费的时间
-t 在每行数据之前打印目标 Java 进程的启动时间。通过比较 Java 进程的启动时间以及总 GC 时间(GCT 列)或两次测量的间隔时间以及总 GC 时间的增量,来得出 GC 时间占运行时间的比例。
-gc 监视 Java 堆状况,包括 Eden、Survivor、老年代等的容量,已用空间,垃圾收集时间合计等信息
-gccapacity 监视内容与 -gc 基本相同,但输出主要关注 Java 堆各个区域使用到的最大、最小空间
-gcutil 监视内容与 -gc 基本相同,但输出主要关注已使用空间占总空间的百分比
-gccause 与 -gcutil 功能一样,但会额外输出导致上一次垃圾收集产生的原因
-compiler 输出即时编译器编译过的方法、耗时等信息

jstat 的执行样例:
image.png
具体含义如下所示:

  • 新生代 Eden 区(E)使用了 52.08% 的空间,两个 Survivor 区(S0S1)里面其中一个是空,另一个使用了 99.96% 的空间。
  • 老年代(O)和元空间(M)则分别使用了 19.03% 和 95.19% 的空间。
  • 程序运行以来共发生 Minor GC(YGC)17 次,总耗时(YGCT)为 0.193 秒。
  • 发生 Full GC(FGC)3 次,总耗时(FGCT)为 0.207 秒。
  • 所有 GC 总耗时(GCT)为 0.400 秒。

如果我们使用的垃圾回收器是 G1 时,输出结果则有另一些特征:

  1. $ jstat -gc 22208 1s
  2. S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT CGC CGCT GCT
  3. 0,0 16384,0 0,0 16384,0 210944,0 192512,0 133120,0 5332,5 28848,0 26886,4 4864,0 4620,5 19 0,067 1 0,016 2 0,002 0,084
  4. 0,0 16384,0 0,0 16384,0 210944,0 83968,0 133120,0 5749,9 29104,0 27132,8 4864,0 4621,0 21 0,078 1 0,016 2 0,002 0,095
  5. 0,0 0,0 0,0 0,0 71680,0 18432,0 45056,0 20285,1 29872,0 27952,4 4864,0 4671,6 23 0,089 2 0,063 2 0,002 0,153
  6. 0,0 2048,0 0,0 2048,0 69632,0 28672,0 45056,0 18608,1 30128,0 28030,4 4864,0 4672,4 32 0,093 2 0,063 2 0,002 0,158
  7. ...

可以看到,S0C 和 S0U 始终为 0,而且另一个 Survivor 区的容量(S1C)可能会下降至 0。这是因为在使用 G1 GC 时,Java 虚拟机不再设置 Eden 区、Survivor 区、老年代区的内存边界,而是将堆划分为若干个等长内存区域。每个内存区域都可以作为 Eden 区、Survivor 区以及老年代区中的任一种,并且可以在不同区域类型之间来回切换。

换句话说,逻辑上我们只有一个 Survivor 区。当需要迁移 Survivor 区中的数据时,我们只需另外申请一个或多个内存区域作为新的 Survivor 区。因此,Java 虚拟机决定在使用 G1 GC 时,将所有 Survivor 内存区域的总容量以及已使用量存放至 S1C 和 S1U 中,而 S0C 和 S0U 则被设置为 0。

当发生垃圾回收时,Java 虚拟机可能出现 Survivor 内存区域内的对象全被回收或晋升的现象。在这种情况下,Java 虚拟机会将这块内存区域回收,并标记为可分配的状态。这样子做的结果是,堆中可能完全没有 Survivor 内存区域,因而相应的 S1C 和 S1U 将会是 0。

jinfo

jinfo(Configuration Info for Java)用来实时查看和调整虚拟机各项参数,如传递给 Java 虚拟机的-X(即输出中的 jvm_args)、-XX参数(即输出中的 VM Flags)以及可在 Java 层面通过 System.getProperty 获取的 -D 参数(即输出中的 System Properties)。

虽然使用 jps 命令的 -v 参数可以查看虚拟机启动时显式指定的参数列表,但如果想知道未被显式指定的参数的系统默认值,就只能使用 jinfo 的 -flags 选项进行查询了。

jinfo 的命令格式:

  1. jinfo [option] <vmid>
  • -flag <name>:打印指定 Java 虚拟机的参数值。
  • -flag [+|-]<name>:指定 Java 虚拟机参数的布尔值。
  • -flag =<value>:指定 Java 虚拟机参数的值。当然这个修改能力是极其有限的。

jinfo 的执行样例:
image.png

jmap

jmap(Memory Map for Java)命令用于生成堆转储快照(heapdump)。jmap 的作用并不仅仅是为了获取堆转储快照,它还可以查询 finalize 执行队列、Java 堆和方法区的详细信息,如空间使用率、当前用的是哪种收集器等。

jmap 的命令格式:

  1. jmap [option] <vmid>
  • option:可选值如下 | 选项 | 作用 | | —- | —- | | -clstats | 打印被加载类的信息 | | -dump | 生成 Java 堆转储快照。格式为 -dump:

    - -dump:转储堆中的所有对象
    - -dump:live:转储堆中所有活着的对象。因为会触发一次 FullGC
    | | -finalizerinfo | 显示在 F-Queue 中等待 Finalizer 线程执行 finalize 方法的对象。 | | -heap | 显示 Java 堆详细信息,如使用哪种回收器、参数配置、分代状况等 | | -histo | 显示 Java 堆中对象统计信息,包括类、实例数量、合计容量。 |

我们通常会利用 jmap -dump:live,format=b,file=filename.bin 命令,将堆中所有存活对象导出至一个文件之中。因为 jmap -dump:live 会触发一次 FullGC。这里 format=b 将使 jmap 导出与 -XX:+HeapDumpAfterFullGC、-XX:+HeapDumpOnOutOfMemoryError 格式一致的文件。这种格式的文件可以被其他 GUI 工具查看。

jmap 的执行样例:
image.png
由于 jmap 将访问堆中的所有对象,为了保证在此过程中不被应用线程干扰,jmap 需要借助安全点机制,让所有线程停留在不改变堆中数据的状态。也就是说,由 jmap 导出的堆快照必定是安全点位置的。这可能导致基于该堆快照的分析结果存在偏差。

举个例子,假设在编译生成的机器码中,某些对象的生命周期在两个安全点之间,那么 :live 选项将无法探知到这些对象。另外,如果某个线程长时间无法跑到安全点,jmap 将一直等下去。而 jstat 则不同,这是因为垃圾回收器会主动将 jstat 所需要的摘要数据保存至固定位置,而 jstat 只需直接读取即可。

jmap(以及 jinfo、jstack 和 jcmd)依赖于 Java 虚拟机的 Attach API,因此只能监控本地 Java 进程。一旦开启 Java 虚拟机参数 DisableAttachMechanism,基于 Attach API 的命令将无法执行。反过来说,如果你不想被其他进程监控,那么你需要开启该参数。

jstack

jstack(Stack Trace for Java)命令用于生成虚拟机当前时刻的线程快照(threaddump)。线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的目的通常是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间挂起等,都是导致线程长时间停顿的常见原因。

jstack 的命令格式:

  1. jstack [-l] [-e] <vmid>
  • -l:除堆栈外,显示关于锁的附加信息
  • -m:如果调用到本地方法的话,可以显示 C、C++ 的堆栈
  • -F:强制执行 Thread Dump,可在 Java 进程卡死时使用,此选项可能需要系统权限

jstack 的执行样例:

  1. jstack -l 47583 > thread.dump

保存后的文件是一个文本文件,这里提供一个在线的 threaddump 分析网站

jcmd

jcmd 是 JDK 8 推出的一款本地诊断工具(文档),只支持连接本机上同一个用户空间下的 JVM 进程。它可以替代前面除了 jstat 之外的所有命令。

jcmd 的命令格式:

  1. jcmd [pid] [command]

其中,command 包括:

  1. Compiler.CodeHeap_Analytics
  2. Compiler.codecache
  3. Compiler.codelist
  4. Compiler.directives_add
  5. Compiler.directives_clear
  6. Compiler.directives_print
  7. Compiler.directives_remove
  8. Compiler.queue
  9. GC.class_histogram
  10. GC.class_stats
  11. GC.finalizer_info
  12. GC.heap_dump
  13. GC.heap_info
  14. GC.run
  15. GC.run_finalization
  16. VM.class_hierarchy
  17. VM.classloader_stats
  18. VM.classloaders
  19. VM.command_line
  20. VM.dynlibs
  21. VM.flags
  22. VM.info
  23. VM.log
  24. VM.metaspace
  25. VM.native_memory
  26. VM.print_touched_methods
  27. VM.set_flag
  28. VM.stringtable
  29. VM.symboltable
  30. VM.system_properties
  31. VM.systemdictionary
  32. VM.unlock_commercial_features
  33. VM.uptime
  34. VM.version