3.2 Java监控工具
要想深入了解 JVM 自身,需要使用 Java 的监控工具。JDK 自带以下所列工具。
jcmd
它用来打印 Java 进程所涉及的基本类、线程和 VM 信息。它适用于脚本,可以像这 样执行:
% jcmd process_id command optional_arguments
jcmd help 可以列出所有的命令。jcmd help 可以给出特定命令的语法。
jconsole
提供 JVM 活动的图形化视图,包括线程的使用、类的使用和 GC 活动。
jhat
读取内存堆转储,并有助于分析。这是事后使用的工具。
jmap
提供堆转储和其他 JVM 内存使用的信息。可以适用于脚本,但堆转储必须在事后分析工具中使用。
jinfo
查看 JVM 的系统属性,可以动态设置一些系统属性。可适用于脚本。
jstack
转储 Java 进程的栈信息。可适用于脚本。
jstat
提供 GC 和类装载活动的信息。可适用于脚本。
jvisualvm
监视 JVM 的 GUI 工具,可用来剖析运行的应用,分析 JVM 堆转储(事后活动,虽然 jvisualvm 也可以实时抓取程序的堆转储)。
这些工具可广泛用于以下领域:
基本的 VM 信息
线程信息
类信息
实时 GC 分析
堆转储的事后处理
JVM 的性能分析
你可能注意到了,工具和适用领域并非一一对应的,许多工具可用于多个领域。所以我们不是单个研究每个工具,而是着眼于 Java 重要的可观测领域,讨论这些工具如何提供这类信息。同时,我们还会讨论其他工具(有些是开源,有些是商业),虽然提供的基本功能相同,但是相比基本的 JDK 工具具有一定的优势。
3.2.1 基本的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 版本
用以下方式获取 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 可以设置许多调优标志,本书就关注了其中很多标志。追踪这些标志及其默认值有点让人崩溃。上面最后两个 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 可用的调优参数(比如 JDK 7u40 有 668 个可能的标志)。绝大多数标志的存在都是为了支持工程师收集更多与应用运行(以及错误行为)相关的信息。有个比较吸引人的标志称为
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 的标志有效。
快速小结
1.
jcmd可用来查找运行中的应用所在 JVM 的基本信息——包括所有调优标志的值。2. 命令行上添加
-XX:+Printflagsfinal可输出标志的默认值。这在查看特定平台自动优化所判定的默认值时很有用。3.
jinfo在检查(某些情况下可以更改)单个标志时很有用。
3.2.2 线程信息
jconsole 和 jvisualvm 可以实时显示应用中运行的线程的数量。
查看运行线程的栈信息,对于判断线程是否被阻塞很有用。可以通过 jstack 获取栈信息:
% jstack process_id…… 显示了每个线程的栈的众多输出 ……
也可以通过 jcmd 获取栈信息:
% jcmd process_id Thread.print…… 显示了每个线程的栈的众多输出 ……
监控线程栈的详情请参见第 9 章。
3.2.3 类信息
jconsole 或 jstat 可以提供应用已使用类的个数。jstat 还能提供类编译相关的信息。
应用使用类的更多细节请参见第 12 章,监控类编译的细节请参见第 4 章。
3.2.4 实时GC分析
几乎所有的监控工具都能报告一些 GC 活动的信息。jconsole 可以用实时图显示堆的使用情况。jcmd 可以执行 GC 操作。jmap 可以打印堆的概况、永久代信息或者创建堆转储。jstat 可以为垃圾收集器正在执行的操作生成许多视图。
想了解这些程序如何监控 GC 活动,请参见第 5 章的实例。
3.2.5 事后堆转储
jvisualvm 的 GUI 界面可以捕获堆转储,也可以用命令行 jcmd 或 jmap 生成。堆转储是堆使用情况的快照,可以用不同的工具进行分析,包括 jvisualvm 和 jhat。传统上,第三方处理堆转储的工具都领先于 JDK,所以第 7 章使用第三方工具——Eclipse Memory Analyzer Tool——为例,展示如何在事后处理堆转储。
快速小结