摘要
给一个系统定位问题的时候, 知识、 经验是关键基础, 数据是依据, 工具是运用知识处理数据的手段。 这里说的数据包括:运行日志、 异常堆栈、 GC日志、 线程快照( threaddump/javacore文件) 、 堆转储快照( heapdump/hprof文件) 等。 经常使用适当的虚拟机监控和分析的工具可以加快我们分析数据、 定位解决问题的速度, 但在学习工具前, 也应当意识到工具永远都是知识技能的一层包装, 没有什么工具是“秘密武器”, 不可能学会了就能包治百病。本博文将详细介绍的JDK性能监控与调优工具,帮助大家在测试与生产环境中更好的发现问题和解决问题。
一、JVM 性能指标
二、JVM 调优工具
2.1 jps 虚拟机进程状况
JDK的很多小工具的名字都参考了UNIX命令的命名方式, jps( JVM Process StatusTool) 是其中的典型。 除了名字像UNIX的ps命令之外, 它的功能也和ps命令类似: 可以列出正在运行的虚拟机进程, 并显示虚拟机执行主类( Main Class,main( ) 函数所在的类) 名称以及这些进程的本地虚拟机唯一ID( Local Virtual Machine Identifier,LVMID) 。 虽然功能比较单一, 但它是使用频率最高的JDK命令行工具, 因为其他的JDK工具大多需要输入它查询到的LVMID来确定要监控的是哪一个虚拟机进程。 对于本地虚拟机进程来说, LVMID与操作系统的进程ID( Process Identifier,PID) 是一致的, 使用Windows的任务管理器或者UNIX的ps命令也可以查询到虚拟机进程的LVMID, 但如果同时启动了多个虚拟机进程, 无法根据进程名称定位时, 那就只能依赖jps命令显示主类的功能才能区分了。
jsp命令格式:
jps[options][hostid]
jps执行样例:
D: \Develop\Java\jdk1.6.0_21\bin> jps-l
2388 D: \Develop\glassfish\bin\..\modules\admin-cli.jar
2764 com.sun.enterprise.glassfish.bootstrap.ASMain
3788 sun.tools.jps.Jps
2.2 jstat 虚拟机统计信息监视
jstat(JVM Statistics Monitoring Tool):用于监视虚拟机各种运行状态信息的命令行工具。它可以显示本地或者远程虚拟机进程中的类装载、内存、垃圾收集、JIT 编译等运行数据。在没有 GUI 图形界面,只提供了纯文本控制台环境的服务器上,它将是运行期定位虚拟机性能问题的首选工具。常用于检测垃圾回收问题以及内存泄漏问题。
基本使用语法为:jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]
查看命令相关参数:jstat-h 或 jstat-help
其中 vmid 是进程 id 号,也就是 jps 之后看到的前面的号码,如下:
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.3 jinfo Java配置信息
jinfo(Configuration Info for Java):查看虚拟机配置参数信息,也可用于调整虚拟机的配置参数。在很多情况卡,Java 应用程序不会指定所有的 Java 虚拟机参数。而此时,开发人员可能不知道某一个具体的 Java 虚拟机参数的默认值。在这种情况下,可能需要通过查找文档获取某个参数的默认值。这个查找过程可能是非常艰难的。但有了 jinfo 工具,开发人员可以很方便地找到 Java 虚拟机参数的当前值。
基本使用语法为:jinfo [options] pid
jinfo 17800
说明:java 进程 ID 必须要加上
选项 | 选项说明 |
---|---|
no option | 输出全部的参数和系统属性 |
-flag name | 输出对应名称的参数 |
-flag [+-]name | 开启或者关闭对应名称的参数 只有被标记为 manageable 的参数才可以被动态修改 |
-flag name=value | 设定对应名称的参数 |
-flags | 输出全部的参数 |
-sysprops | 输出系统属性 |
jinfo -sysprops
> jinfo -sysprops
jboss.modules.system.pkgs = com.intellij.rt
java.vendor = Oracle Corporation
sun.java.launcher = SUN_STANDARD
sun.management.compiler = HotSpot 64-Bit Tiered Compilers
catalina.useNaming = true
os.name = Windows 10
...
jinfo -flags
> jinfo -flags 25592
Non-default VM flags: -XX:CICompilerCount=4 -XX:InitialHeapSize=333447168 -XX:MaxHeapSize=5324668928 -XX:MaxNewSize=1774714880 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=111149056 -XX:OldSize=222298112 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
Command line: -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:8040,suspend=y,server=n -Drebel.base=C:\Users\Vector\.jrebel -Drebel.env.ide.plugin.version=2021.1.2 -Drebel.env.ide.version=2020.3.3 -Drebel.env.ide.product=IU -Drebel.env.ide=intellij -Drebel.notification.url=http://localhost:7976 -agentpath:C:\Users\Vector\AppData\Roaming\JetBrains\IntelliJIdea2020.3\plugins\jr-ide-idea\lib\jrebel6\lib\jrebel64.dll -Dmaven.home=D:\eclipse\env\maven -Didea.modules.paths.file=C:\Users\Vector\AppData\Local\JetBrains\IntelliJIdea2020.3\Maven\idea-projects-state-596682c7.properties -Dclassworlds.conf=C:\Users\Vector\AppData\Local\Temp\idea-6755-mvn.conf -Dmaven.ext.class.path=D:\IDEA\plugins\maven\lib\maven-event-listener.jar -javaagent:D:\IDEA\plugins\java\lib\rt\debugger-agent.jar -Dfile.encoding=UTF-8
jinfo -flag
> jinfo -flag UseParallelGC 25592
-XX:+UseParallelGC
> jinfo -flag UseG1GC 25592
-XX:-UseG1GC
jinfo -flag name
> jinfo -flag UseParallelGC 25592
-XX:+UseParallelGC
> jinfo -flag UseG1GC 25592
-XX:-UseG1GC
jinfo -flag [+-]name
> jinfo -flag +PrintGCDetails 25592
> jinfo -flag PrintGCDetails 25592
-XX:+PrintGCDetails
> jinfo -flag -PrintGCDetails 25592
> jinfo -flag PrintGCDetails 25592
-XX:-PrintGCDetails
java -XX:+PrintFlagsInitial 查看所有 JVM 参数启动的初始值
[Global flags]
intx ActiveProcessorCount = -1 {product}
uintx AdaptiveSizeDecrementScaleFactor = 4 {product}
uintx AdaptiveSizeMajorGCDecayTimeScale = 10 {product}
uintx AdaptiveSizePausePolicy = 0 {product}
...
java -XX:+PrintFlagsFinal 查看所有 JVM 参数的最终值
[Global flags]
intx ActiveProcessorCount = -1 {product}
...
intx CICompilerCount := 4 {product}
uintx InitialHeapSize := 333447168 {product}
uintx MaxHeapSize := 1029701632 {product}
uintx MaxNewSize := 1774714880 {product}
java -XX:+PrintCommandLineFlags 查看哪些已经被用户或者 JVM 设置过的详细的 XX 参数的名称和值
-XX:InitialHeapSize=332790016 -XX:MaxHeapSize=5324640256 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
2.4 jmap Java内存映像
jmap(JVM Memory Map):作用一方面是获取 dump 文件(堆转储快照文件,二进制文件),它还可以获取目标 Java 进程的内存相关信息,包括 Java 堆各区域的使用情况、堆中对象的统计信息、类加载信息等。开发人员可以在控制台中输入命令“jmap -help”查阅 jmap 工具的具体使用方式和一些标准选项配置。
选项 | 作用 |
---|---|
-dump | 生成 dump 文件(Java 堆转储快照),-dump:live 只保存堆中的存活对象 |
-heap | 输出整个堆空间的详细信息,包括 GC 的使用、堆配置信息,以及内存的使用信息等 |
-histo | 输出堆空间中对象的统计信息,包括类、实例数量和合计容量,-histo:live 只统计堆中的存活对象 |
-J <flag> | 传递参数给 jmap 启动的 jvm |
-finalizerinfo | 显示在 F-Queue 中等待 Finalizer 线程执行 finalize 方法的对象,仅 linux/solaris 平台有效 |
-permstat | 以 ClassLoader 为统计口径输出永久代的内存状态信息,仅 linux/solaris 平台有效 |
-F | 当虚拟机进程对-dump 选项没有任何响应时,强制执行生成 dump 文件,仅 linux/solaris 平台有效 |
jmap命令格式:
jmap[option]vmid
-dump:[live,]format=b,file=<filename> 使用hprof二进制形式,输出jvm的heap内容到文件=. live子选项是可选的,假如指定live选项,那么只输出活的对象到文件.
jmap -dump:live,format=b,file=myjmapfile.txt 19570
finalizerinfo 打印正等候回收的对象的信息
jmap -finalizerinfo 3772
2.5 jhat 虚拟机堆转储快照分析
jhat(JVM Heap Analysis Tool):Sun JDK 提供的 jhat 命令与 jmap 命令搭配使用,用于分析 jmap 生成的 heap dump 文件(堆转储快照)。jhat 内置了一个微型的 HTTP/HTML 服务器,生成 dump 文件的分析结果后,用户可以在浏览器中查看分析结果(分析虚拟机转储快照信息)。
使用了 jhat 命令,就启动了一个 http 服务,端口是 7000,可以再本地的浏览器进行的访问。说明:jhat 命令在 JDK9、JDK10 中已经被删除,官方建议用 VisualVM 代替。基本适用语法:jhat <option> <dumpfile>
option 参数 | 作用 |
---|---|
-stack false | true | 关闭|打开对象分配调用栈跟踪 |
-refs false | true | 关闭|打开对象引用跟踪 |
-port port-number | 设置 jhat HTTP Server 的端口号,默认 7000 |
-exclude exclude-file | 执行对象查询时需要排除的数据成员 |
-baseline exclude-file | 指定一个基准堆转储 |
-debug int | 设置 debug 级别 |
-version | 启动后显示版本信息就退出 |
-J <flag> | 传入启动参数,比如-J-Xmx512m |
jmap -dump:live,file=a.log pid
除了使用jmap命令,还可以通过以下方式:
1、使用 jconsole 选项通过 HotSpotDiagnosticMXBean 从运行时获得堆转储(生成dump文件)、
2、虚拟机启动时如果指定了 -XX:+HeapDumpOnOutOfMemoryError 选项, 则在抛出 OutOfMemoryError 时, 会自动执行堆转储。
3、使用 hprof 命令
2.6 jstack Java堆栈跟踪
jstack(JVM Stack Trace):用于生成虚拟机指定进程当前时刻的线程快照(虚拟机堆栈跟踪)。线程快照就是当前虚拟机内指定进程的每一条线程正在执行的方法堆栈的集合。
生成线程快照的作用:可用于定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等问题。这些都是导致线程长时间停顿的常见原因。当线程出现停顿时,就可以用 jstack 显示各个线程调用的堆栈情况。
在 thread dump 中,要留意下面几种状态
- 死锁,Deadlock(重点关注)
- 等待资源,Waiting on condition(重点关注)
- 等待获取监视器,Waiting on monitor entry(重点关注)
- 阻塞,Blocked(重点关注)
- 执行中,Runnable
- 暂停,Suspended
- 对象等待中,Object.wait() 或 TIMED_WAITING
- 停止,Parked
option 参数 | 作用 |
---|---|
-F | 当正常输出的请求不被响应时,强制输出线程堆栈 |
-l | 除堆栈外,显示关于锁的附加信息 |
-m | 如果调用本地方法的话,可以显示 C/C++的堆栈 |
jstack命令格式:
jstack[option]vmid
jstack [ option ] pid
jstack [ option ] executable core
jstack [ option ] [server-id@]remote-hostname-or-IP
2.7 VisualVM 多合一故障处理
VisualVM( All-in-One Java Troubleshooting Tool) 是到目前为止随JDK发布的功能最强大的运行监视和故障处理程序, 并且可以预见在未来一段时间内都是官方主力发展的虚拟机故障处理工具。 官方在VisualVM的软件说明中写上了“All-in-One”的描述字样, 预示着它除了运行监视、 故障处理外, 还提供了很多其他方面的功能。 如性能分析( Profiling) , VisualVM的性能分析功能甚至比起JProfiler、 YourKit等专业且收费的Profiling工具都不会逊色多少, 而且VisualVM的还有一个很大的优点: 不需要被监视的程序基于特殊Agent运行, 因此它对应用程序的实际性能的影响很小, 使得它可以直接应用在生产环境中。 这个优点是JProfiler、 YourKit等工具无法与之媲美的。
内存堆Heap,首先我们来看内存堆Heap使用情况,我本机eclipse的进程在visualVM显示如下:
2.8 jcmd—多功能命令行
在 JDK 1.7 以后,新增了一个命令行工具 jcmd。它是一个多功能的工具,可以用来实现前面除了 jstat 之外所有命令的功能。比如:用它来导出堆、内存使用、查看 Java 进程、导出线程信息、执行 GC、JVM 运行时间等。jcmd 拥有 jmap 的大部分功能,并且在 Oracle 的官方网站上也推荐使用 jcmd 命令代 jmap 命令
- jcmd -l:列出所有的 JVM 进程
- jcmd 进程号 help:针对指定的进程,列出支持的所有具体命令
- jcmd 进程号 具体命令:显示指定进程的指令命令的数据
- 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 的配置参数信息
2.9 jstatd—远程主机信息收集
之前的指令只涉及到监控本机的 Java 应用程序,而在这些工具中,一些监控工具也支持对远程计算机的监控(如 jps、jstat)。为了启用远程监控,则需要配合使用 jstatd 工具。命令 jstatd 是一个 RMI 服务端程序,它的作用相当于代理服务器,建立本地计算机与远程监控工具的通信。jstatd 服务器将本机的 Java 应用程序信息传递到远程计算机。
三、JVM可视化调优工具
3.1 JConsole
JConsole,顾名思义,就是“Java 控制台”,在这里,我们可以从多个维度和时间范围去监控一个 Java 进程的内外部指标。进而通过这些指标数据来分析判断 JVM 的状态,为我们的调优提供依据。
上图中显示了总共 6 个标签页,每个标签页对应一个监控面板,分别为:
- 概览:以图表方式查看 Java 进程的堆内存、线程、类、CPU 占用率四项指标和历史。
- 内存:JVM 的各个内存池的使用情况以及明细。
- 线程:JVM 内所有的线程列表和具体的状态信息。
- 类:JVM 加载和卸载的类数量汇总信息。
- VM 概要:JVM 的供应商、运行时间、JVM 参数,以及其他数据的摘要。
- MBean:跟 JMX 相关的 MBean,我们在后面的 JMX 课程中进行讲解。
3.2 JVisualVM
在命令行或者运行窗口直接输入 jvisualvm 即可启动:
3.3 JMC
JMC 和 JVisualVM 功能类似,因为 JMC 的前身是 JRMC,JRMC 是 BEA 公司的 JRockit JDK 自带的分析工具,被 Oracle 收购以后,整合成了 JMC 工具。Oracle 试图用 JMC 来取代 JVisualVM,在商业环境使用 JFR 需要付费获取授权。在命令行输入 jmc 后,启动后的界面如下:
四、调优分析工具
内存 Dump 分为 2 种方式:主动 Dump 和被动 Dump。
- 主动 Dump 的工具包括:jcmd、jmap、JVisualVM 等等。具体使用请参考相关工具部分。
- 被动 Dump 主要是:hprof,以及
-XX:+HeapDumpOnOutOfMemoryError
等参数。
常用的分析工具有:
- jhat:jhat 用来支持分析 dump 文件,是一个 HTTP/HTML 服务器,能将 dump 文件生成在线的 HTML 文件,通过浏览器查看。
- MAT:MAT 是比较好用的、图形化的 JVM Dump 文件分析工具。
好用的分析工具
- MAT 全称是 Eclipse Memory Analyzer Tools。其优势在于,可以从 GC root 进行对象引用分析,计算各个 root 所引用的对象有多少,比较容易定位内存泄露。
JDK 内置故障排查工具:
jhat 是 Java 堆分析工具(Java heap Analyzes Tool)。在 JDK6u7 之后成为 JDK 标配。使用该命令需要有一定的 Java 开发经验,官方不对此工具提供技术支持和客户服务。