Java性能调优04——Java性能调优工具箱(中):Java监控和性能分析工具

本系列均取自《Java性能权威指南》这里只是一个个人备忘笔录

Java监控工具

要想深入了解 JVM 自身,需要使用 Java 的监控工具。JDK 自带以下所列工具。

jcmd
用来打印 Java 进程所涉及的基本类、线程和 VM 信息。它适用于脚本,可以像这 样执行:

% jcmd process_id command optional_arguments

jcmd help 可以列出所有的命令。
jcmd help <command> 可以给出特定命令的语法。


jconsole
提供 JVM 活动的图形化视图,包括线程的使用、类的使用和 GC 活动。


jhat
读取内存堆转储,并有助于分析。这是事后使用的工具。


jmap
提供堆转储和其他 JVM 内存使用的信息。可以适用于脚本,但堆转储必须在事后分析工具中使用。


jinfo
查看 JVM 的系统属性,可以动态设置一些系统属性。可适用于脚本。


jstack
转储 Java 进程的栈信息。可适用于脚本。


jstat
提供 GC 和类装载活动的信息。可适用于脚本。


jvisualvm
监视 JVM 的 GUI 工具,可用来剖析运行的应用,分析 JVM 堆转储(事后活动,虽然 jvisualvm 也可以实时抓取程序的堆转储)。


这些工具可广泛用于以下领域:

  1. 基本的 VM 信息
  2. 线程信息
  3. 类信息
  4. 实时 GC 分析
  5. 堆转储的事后处理
  6. JVM 的性能分析

你可能注意到了,工具和适用领域并非一一对应的,许多工具可用于多个领域。所以我们不是单个研究每个工具,而是着眼于 Java 重要的可观测领域,讨论这些工具如何提供这类信息。同时,我们还会讨论其他工具(有些是开源,有些是商业),虽然提供的基本功能相同,但是相比基本的 JDK 工具具有一定的优势。

 基本的VM信息

JVM 工具可以提供 JVM 进程的基本运行信息:它运行多久了,使用哪些 JVM 标志,以及 JVM 的系统属性,等等。

运行时间

此命令可以查看 JVM 运行的时长:

% jcmd process_id VM.uptime

系统属性

以下命令可以显示 System.getProperties() 的各个条目。

% jcmd process_id VM.system_properties

% jinfo -sysprops process_id

这包括通过命令行 -D 标志设置的所有属性,应用动态添加的所有属性和 JVM 的默认属性。

JVM版本

% jcmd process_id VM.version

JVM 命令行

jconsole 的“VM 摘要”页可以显示程序所用的命令行,或者用 jcmd 显示:

% jcmd process_id VM.command_line

JVM 调优标志

可用以下方式获得对应用生效的 JVM 调优标志:

% jcmd process_id VM.flags [-all]

调优标志

JVM 可以设置许多调优标志(JVM参数)。追踪这些标志及其默认值有点让人崩溃。上面最后两个 jcmd 示例对于获取这类信息很有用。command_line 显示直接在命令行指定的标志。flags 显示命令行设置的标志,以及 JVM 直接设置的标志(因为它们的值是通过自动优化决定的)。该命令加上 all 时,可以列出 JVM 内部所有的标志。

有几百个 JVM 调优标志,大多数都很令人费解,而且我们建议永远都不要对其作更改(参见下文的“信息太多?”)。诊断性能问题时,找出哪些标志起作用是很常见的事。JVM 运行时,可以用 jcmd 做到这一点。如果想找出特定 JVM 的平台特定的默认值是什么,那么在命令行上添加 -XX:+Printflagsfinal 会很有用。

想知道特定平台所设置的标志是什么,可以执行以下命令:

% java other_options -XX:+PrintFlagsFinal -version
……几百行输出,包括……
uintx InitialHeapSize                          := 4169431040    {product}
intx InlineSmallCode                           = 2000           {pd product}

你应该在命令行包括所有标志,因为有些标志会影响其他标志,特别是 GC 相关的标志。这个命令会打印 JVM 标志及其取值的完整列表(结果和 jcmd 结合 VM.flags -all 打印的相同)。

这些命令的标志数据以上述两种方式之一显示。输出第 1 行中的冒号表示标志使用的是非默认值。发生这种情况,可能是以下原因导致:

  1. 标志值直接在命令行指定。

  2. 其他标志间接改变了该标志的值。

  3. JVM 自动优化计算出来的默认值。

第 2 行(直接等于,没有冒号)表示,值是这个 JVM 版本的默认值。某些标志的默认值在不同平台上可能会不相同,输出的最右列会指示。product 表示在所有平台上的默认设置都是一致的。pd product 表示标志的默认值是独立于平台的。

信息太多

PrintFlagsFinal 会打出好几百个 JVM 可用的调优参数。

绝大多数标志的存在都是为了支持工程师收集更多与应用运行(以及错误行为)相关的信息。有个比较吸引人的标志称为 AllocatePrefetchLines(默认值为 3),它使得标志值可以改变,从而使预读指令在特定处理器上可以工作得更好。但这种随意的调优并没有必要。除非你有很充分的理由,否则不要更改标志值。就 AllocatePrefetchLines 标志来说,需要掌握应用预读性能、运行应用的 CPU 的特性以及更改这个数字对 JVM 自身的代码有什么影响。

最后一列可能的值还有 manageable(运行时可以动态更改标志的值)和 C2 diagnostic(为编译器工程师提供诊断输出,帮助理解编译器正以什么方式运作)。

还有另一种查看运行中的应用的此类信息的工具,叫作 jinfo。jinfo 的好处在于,它允许程序在执行时更改某个标志的值。

以下是如何获取进程中所有标志的值:

% jinfo -flags process_id

jinfo 带有 -flags 时可以提供所有标志的信息,否则只打印命令行所指定的标志。这两种数据都不像 -XX:+Printflagsfinal 那样易读,但 jinfo 有其他值得注意的特性。

jinfo 可以检查单个标志的值:

% jinfo -flag PrintGCDetails process_id
-XX:+PrintGCDetails

虽然 jinfo 本身不会显示是否 manageable,但 manageable(如 Printflagsfinal 输出中所标识的)的标志可以通过 jinfo 开启或关闭:

% jinfo -flag -PrintGCDetails process_id # turns off PrintGCDetails
% jinfo -flag PrintGCDetails process_id
-XX:-PrintGCDetails

需要当心的是,jinfo 可以更改任意标志的值,但并不意味着 JVM 会响应更改。比如说,大多数影响 GC 算法行为的标志都在启动时使用,以决定垃圾收集器的行为方式。之后通过 jinfo 更改标志值,并不会导致 JVM 改变它的行为。它会以初始时的算法继续执行。所以这个技术只会对那些在 Printflagsfinal 输出中标记为 manageable 的标志有效。

VM信息小结

  1. jcmd 可用来查找运行中的应用所在 JVM 的基本信息——包括所有调优标志的值。
  2. 命令行上添加 -XX:+Printflagsfinal 可输出标志的默认值。这在查看特定平台自动优化所判定的默认值时很有用。
  3. jinfo 在检查(某些情况下可以更改)单个标志时很有用。

线程信息

jconsole 和 jvisualvm 可以实时显示应用中运行的线程的数量。

查看运行线程的栈信息,对于判断线程是否被阻塞很有用。可以通过 jstack 获取栈信息:

% jstack process_id
…… 显示了每个线程的栈的众多输出 ……

也可以通过 jcmd 获取栈信息:

% jcmd process_id Thread.print
…… 显示了每个线程的栈的众多输出 ……

监控线程栈的详情见后面章节。

类信息

jconsole 或 jstat 可以提供应用已使用类的个数。jstat 还能提供类编译相关的信息。

应用使用类的更多细节和监控类编译的细节见后面章节。

实时GC分析

几乎所有的监控工具都能报告一些 GC 活动的信息。jconsole 可以用实时图显示堆的使用情况。jcmd 可以执行 GC 操作。jmap 可以打印堆的概况、永久代信息或者创建堆转储。jstat 可以为垃圾收集器正在执行的操作生成许多视图。

详细的见后

事后堆转储

jvisualvm 的 GUI 界面可以捕获堆转储,也可以用命令行 jcmd 或 jmap 生成。堆转储是堆使用情况的快照,可以用不同的工具进行分析,包括 jvisualvm 和 jhat。传统