JVM 调优首先要做的就是监控 JVM 的运行状态,这就需要用到各种官方和第三方的工具包了
一、 JDK 工具包
JDK 自带的 JVM 工具可以分为命令行工具和可视化工具
- 命令行工具
- jps: JVM Process status tool:JVM进程状态工具,查看进程基本信息
- jstat: JVM statistics monitoring tool:JVM统计监控工具,查看堆,GC详细信息
- jinfo:Java Configuration Info:查看配置参数信息,支持部分参数运行时修改
- jmap:Java Memory Map:分析堆内存工具,dump堆内存快照
- jhat:Java Heap Analysis Tool:堆内存dump文件解析工具
- jstack:Java Stack Trace:Java堆栈跟踪工具
- 可视化工具
- VisualVM:性能分析可视化工具
1.1 命令 jps (列出 java 进程)
jps 工具全称,Java Viture Machine Process Status Tool
作用:查看 Java 进程信息,用来列出 Java 进程
jps :列出Java程序进程ID和Main函数名称
jps -q :只输出进程ID
jps -m :输出传递给Java进程(主函数)的参数
jps -l :输出主函数的完整路径
jps -v :显示传递给Java虚拟机的参数
案例jps
命令:jps -l
C:\Users\StoneYu>jps -l
11040 org.jetbrains.jps.cmdline.Launcher
15968 sun.tools.jps.Jps
4720 com.anmed.jjld.JjldApplication
13352
24024 org/netbeans/Main
12780 org.jetbrains.idea.maven.server.RemoteMavenServer36
25228 org.jetbrains.idea.maven.server.RemoteMavenServer36
25500 org.jetbrains.idea.maven.server.RemoteMavenServer36
1.2 命令 jstat (监控 java 进程状态)
jstat 全称:JVM statistics Monitoring Tool
**作用:**可以查看 Java 程序运行相关信息,可以通过它查看运行时堆信息的相关情况
参数格式:jstat -<options> [-t] [-h<lines>] <vmid> [<interval> [<count>]]
’
options:必选参数由以下值构成
- -class:显示ClassLoader的相关信息
- -compiler:显示JIT编译的相关信息
- -gc:显示与GC相关信息
- -gccapacity:显示各个代的容量和使用情况
- -gccause:显示垃圾收集相关信息(同-gcutil),同时显示最后一次或当前正在发生的垃圾收集的诱发原因
- -gcnew:显示新生代信息
- -gcnewcapacity:显示新生代大小和使用情况
- -gcold:显示老年代信息
- -gcoldcapacity:显示老年代大小
- -gcpermcapacity:显示永久代大小
- -gcutil:显示垃圾收集信息
**-t 可选参数:**如果包含此选项,则在输出中增加一列显示时间戳,单位是秒,从JVM启动到命令执行的时间。
**-h 可选参数:**用于定义输出时每行的宽度(或字符数),默认值通常是适合大多数终端的宽度。是一个数字,指定了行宽。
必选参数:用于指定要监控的Java进程的虚拟机ID。这个ID可以通过jps命令获取。
[]:可选参数,用于设置两次采样之间的间隔时间(单位为秒)。如果不指定,默认可能不打印多次结果,或者根据选项有不同的默认行为。
[]:可选参数,用于指定总共要采集并打印的样本数量。如果不指定,默认通常只打印一次结果。如果指定了但没有指定, 则会持续打印直到手动中断。
案例 jstat -gc
**功能:**输出进程内存区域大小,以及 GC 的详细信息
**监控命令 **jstat -gc 4720 250 5
进程 id 4720 采样间隔 250ms(也可以手动指定单位,不指定就是 ms) 采样 5 次
**监控结果 **
S0C:年轻代中第一个survivor(幸存区)的容量 (单位kb)
S1C:年轻代中第二个survivor(幸存区)的容量 (单位kb)
S0U :年轻代中第一个survivor(幸存区)目前已使用空间 (单位kb)
S1U :年轻代中第二个survivor(幸存区)目前已使用空间 (单位kb)
EC :年轻代中Eden的容量 (单位kb)
EU :年轻代中Eden目前已使用空间 (单位kb)
OC :Old代的容量 (单位kb)
OU :Old代目前已使用空间 (单位kb)
MC:metaspace的容量 (单位kb)
MU:metaspace目前已使用空间 (单位kb)
CCSC:压缩类空间大小
CCSU:压缩类空间使用大小
YGC :从应用程序启动到采样时年轻代中gc次数
YGCT :从应用程序启动到采样时年轻代中gc所用时间(s)
FGC :从应用程序启动到采样时old代(全gc)gc次数
FGCT :从应用程序启动到采样时old代(全gc)gc所用时间(s)
GCT:从应用程序启动到采样时gc用的总时间(s)
案例 jstat-gcutil
功能:输出的是进程内存区域百分百 及 GC详细信息
案例命令:jstat -gcutil 4720 250 5
监控结果
S0 年轻代中第一个survivor(幸存区)已使用的占当前容量百分比
S1 年轻代中第二个survivor(幸存区)已使用的占当前容量百分比
E 年轻代中Eden(伊甸园)已使用的占当前容量百分比
O old代已使用的占当前容量百分比
M metaspace已使用的占当前容量百分比
CCS 压缩使用比例
YGC 从应用程序启动到采样时年轻代中gc次数
YGCT 从应用程序启动到采样时年轻代中gc所用时间(s)
FGC 从应用程序启动到采样时old代(全gc)gc次数
FGCT 从应用程序启动到采样时old代(全gc)gc所用时间(s)
GCT 从应用程序启动到采样时gc用的总时间(s)
1.3 命令 jinfo (查看甚至修改 java 程序拓展参数)
jinfo : java Configuration Info
作用:查看正在运行的 Java 程序的拓展参数,甚至支持修改运行过程中的部分参数。
格式:jinfo [option] <pid>
-flags 打印虚拟机 VM 参数
-flag <name> 打印指定虚拟机 VM 参数
-flag [+|-]<name> 打开或关闭虚拟机参数
-flag <name>=<value> 设置指定虚拟机参数的值
案例 jinfo
案例命令:jinfo -flags 4720
案例输出
PS C:\Program Files\Java\jdk1.8.0_221\bin> ./jinfo -flags 4720
Attaching to process ID 4720, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.221-b11
Non-default VM flags: -XX:-BytecodeVerificationLocal -XX:-BytecodeVerificationRemote -XX:CICompilerCount=4 -XX:+FlightRecorder -XX:InitialHeapSize=536870912 -XX:+ManagementServer -XX:MaxHeapSize=8573157376 -XX:MaxNewSize=2857369600 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=178782208 -XX:OldSize=358088704 -XX:TieredStopAtLevel=1 -XX:+UnlockCommercialFeatures -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
Command line: -XX:TieredStopAtLevel=1 -Xverify:none -Dspring.output.ansi.enabled=always -javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2020.3\lib\idea_rt.jar=57449:C:\Program Files\JetBrains\IntelliJ IDEA 2020.3\bin -Dcom.sun.management.jmxremote -Dspring.jmx.enabled=true -Dspring.liveBeansView.mbeanDomain -Dspring.application.admin.enabled=true -Dfile.encoding=UTF-8
1.4 命令 jmap (查看内存情况)
jmap : Memory Map
作用:jmap 用来查看堆内存使用情况,一般结合 jhat 使用。dunmp 堆内容时会暂停用户线程,生产环境使用的时候需要注意。并且该命令需要较高权限。
格式:jmap [option] <pid> | <executable core> | [<server-id@]remote-hostname-or-IP>
案例 jmap 打印共享对象及路径
命令: ./jmap 4720
案例 jmap -heap pid 打印堆详细信息
命令:./jmap -heap 4720
这里可以看到堆内存分配了 8g/32G 是我电脑物理内存的 1/4 ,因为我没有指定堆内存大小。
案例 jmap -histo:live pid 打印存活的对象统计信息
命令:jmap -histo:live pid
描述:显示堆中对象的统计信息:其中包括每个Java类、对象数量、内存大小(单位:字节)、完全限定的类名。打印的虚拟机内部的类名称将会带有一个’*’前缀。如果ja指定了live子选项,则只计算活动的对象。
案例 jmap -clstats pid 打印加载器状态信息
命令:jmap -clstats 4720
描述:打印类加载器信息:打印Java堆内存的方法区的类加载器的智能统计信息。对于每个类加载器而言,它的名称、活跃度、地址、父类加载器、它所加载的类的数量和大小都会被打印。此外,包含的字符串数量和大小也会被打印。
案例 jmap -finalizerinfo 4720 查看等待终结的对象信息
命令:jmap -finalizerinfo 4720
描述:打印等待终结的对象信息
案例 jmap: dump 堆数据快照
命令: jmap -dump:format=b,file=heapdump.hprof pid
format=b 表示保存成二进制,这也是最常用的格式
描述:生成堆转储快照dump文件:以二进制格式转储Java堆到指定文件中。如果指定了live子选项,堆中只有活动的对象会被转储。浏览heap dump 可以使用jhat 读取生成的文件,也可以使用MAT等堆内存分析工具。
注意:dump 的过程耗时较长,并且会暂停应用。线上一定慎用
1.5 jhat 堆快照分析工具
jhat:Java Heap Analysis Tool
格式:jhat [ options ] heap-dump-file
- jhat 命令会解析Java堆转储文件,并启动一个 web server。然后用浏览器来查看/浏览 dump 出来的 heap二进制文件。
- jhat 命令支持预先设计的查询,比如:显示某个类的所有实例。还支持 对象查询语言(OQL)。 OQL有点类似SQL,专门用来查询堆转储。
案例 jhat
执行命令:./jhat C:\Users\StoneYu\Desktop\临时文件\testdump.hprof
启动了一个 Server 服务,端口为 7000
1.6 jstack 线程跟踪
jstack:Java Stack Trace
作用
- jstack是Java虚拟机自带的一种堆栈跟踪工具,用于生成java虚拟机当前时刻的线程快照。
- 线程快照是当前Java虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待、等等。
线程快照中需要留意的状态
- 死锁,Deadlock(重点关注)
- 等待资源,Waiting on condition(重点关注)
- 等待获取管程,Waiting on monitor entry(重点关注)
- 阻塞,Blocked(重点关注)
- 执行中,Runnable
- 暂停,Suspended
- 对象等待中,Object.wait() 或 TIMED_WAITING
- 停止,Parked
使用方式
jstack [ option ] pid 查看当前时间点,指定进程的dump堆栈信息。
jstack [ option ] pid > 文件 将当前时间点的指定进程的dump堆栈信息,写入到指定文件中。
# 注:若该文件不存在,则会自动生成; 若该文件存在,则会覆盖源文件。
jstack [ option ] executable core 查看当前时间点,core文件的dump堆栈信息。
jstack [ option ] [server_id@]<remote server IP or hostname> 查看当前时间点,远程机器的dump堆栈信息。
-F # 当进程挂起了,此时'jstack [-l] pid'是没有相应的,这时候可使用此参数来强制打印堆栈信息,强制jstack),一般情况不
需要使用。
-m # 打印java和native c/c++框架的所有栈信息。可以打印JVM的堆栈,以及Native的栈帧,一般应用排查不需要使用。
-l # 长列表. 打印关于锁的附加信息。例如属于java.util.concurrent的ownable synchronizers列表,会使得JVM停顿得长久
得多(可能会差很多倍,比如普通的jstack可能几毫秒和一次GC没区别,加了-l 就是近一秒的时间),-l 建议不要用。一般情况不需
要使用。
-h or -hel # 打印帮助信息
案例 jstack pid 打印堆栈
执行命令 ./jstack 25356
1.7 VisualVM JVM 可视化监控工具
介绍:
VisualVM 是一款免费的性能分析工具。它通过 jvmstat、JMX、SA(Serviceability Agent)以及 Attach API 等多种方式从程序运行时
获得实时数据,从而进行动态的性能分析。同时,它能自动选择更快更轻量级的技术尽量减少性能分析对应用程序造成的影响,提高性能分析的精度。
配置监控远程 JVM 的方法
远程服务需要配置 jmx 监控,配置 rmi 通信端口和地址
-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=9999
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.authenticate=false
-Djava.rmi.server.hostname=172.26.233.198
-Dcom.sun.management.jmxremote.rmi.port=9999
#JAVA_OPT="${JAVA_OPT} -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=9999 -
Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -
Djava.rmi.server.hostname=123.56.254.18 -Dcom.sun.management.jmxremote.rmi.port=9999
二、第三方工具
2.1 GCEasy (GC 日志可视化分析网站)
网站地址:https://gceasy.io/
作用:能直接可视化分析 GC 日志,并给出问题分析和建议。
GCeasy-report-gc-default.pdf
2.2 MAT (Memory Analyzer tool 内存分析工具)
介绍
MAT是一个强大的可视化内存分析工具,可以快捷、有效地帮助我们找到内存泄露,减少内存消耗分析工具。MAT是MemoryAnalyzer tool的缩写,是一种快速,功能丰富的Java堆分析工具,能帮助你查找内存泄漏和减少内存消耗。
功能
- 找到最大的对象,因为MAT提供显示合理的累积大小(retained size)
- 探索对象图,包括inbound和outbound引用,即引用此对象的和此对象引出的
- 查找无法回收的对象,可以计算从垃圾收集器根到相关对象的路径
- 找到内存浪费,比如冗余的String对象,空集合对象。
- 分析内存溢出、分析内存泄露、查看对象个数及对象内存占用、观察对象回收后释放空间大小、观察线程栈
安装
- MAT安装有两种方式,一种是以eclipse插件方式安装,一种是独立安装。
- 独立安装在MAT的官方文档中有相应的安装文件下载,下载地址为:
[https://www.eclipse.org/mat/downloads.php](https://www.eclipse.org/mat/downloads.php)
MAT 的一些概念
- 内存泄漏与内存溢出
- 内存泄露:对象是垃圾了,还存在被GCRoots引用的情况,无法被垃圾收集器回收。解决方案:找出泄漏的代码位置和原因,具体问题具体解决;
- 内存溢出:内存中的对象非常多,堆空间不足,就会出现。解决方案:检查堆大小设置是否合理,检查是否存在对象生命周期太长、持有状态时间过长的情况。
- shallow heap及retained heap
- shallow heap:对象本身占用内存的大小,也就是对象内存区域的总和。
- retained heap:对象及对象引用链中所有对象的大小总和,如果一个对象被释放掉,因为该对象的释放而被释放的所有的对象的大小。相对于shallow heap,Retained heap可以更精确的反映一个对象实际占用的大小。
- outgoing references与incoming references
- outgoing references :表示该对象的出节点(被该对象引用的对象)。
- incoming references :表示该对象的入节点(引用到该对象的对象)。
- Dominator Tree Dominator Tree对象的支配树:帮助我们快速的发现占用内存最大的块,也能帮我们分析对象之间的依赖关系。
2.3 GCViewer 开源的GC日志分析工具
GCViewer是一款开源的GC日志分析工具。项目的 GitHub 主页对各个指标提供了完整的描述信息 需要安装JDK才能使用。借助GCViewer日志分析工具,可以非常直观地分析出待调优点。作为 GCEasy 的备份
可从以下几方面来分析:
- Memory:分析Totalheap、Tenuredheap、Youngheap内存占用率及其他指标,理论上内存占用率越小越好;
- Pause:分析Gc pause、Fullgc pause、Total pause三个大项中各指标,理论上GC次数越少越好,GC时长越小越好;
项目主页:https://github.com/chewiebug/GCViewer
2.4 阿里的 Arthas 线上监控诊断工具
项目官网:https://arthas.aliyun.com/
介绍:Arthas 是一款线上监控诊断产品,通过全局视角实时查看应用 load、内存、gc、线程的状态信息,并能在不修改应用代码的情况下,对业务问题进行诊断,包括查看方法调用的出入参、异常,监测方法执行耗时,类加载信息等,大大提升线上问题排查效率。
Arthas 支持 JDK 6+,支持 Linux、Mac、Winodws,采用命令行交互模式,同时提供丰富的 Tab 自动补全功能,进一步方便进行问
题的定位和诊断。
Arthas 功能:
- 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?
- 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?
- 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?
- 线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!
- 是否有一个全局视角来查看系统的运行状况?
- 有什么办法可以监控到 JVM 的实时运行状态?
- 怎么快速定位应用的热点,生成火焰图?
Arthas 常见命令
jvm:查看当前 JVM 的信息
thread:查看当前 JVM 的线程堆栈信息,
-b 选项可以一键检测死锁
-n 指定最忙的前N个线程并打印堆栈
trace:方法内部调用路径,并输出方法路径上的每个节点上耗时,服务间调用时间过长时使用
stack:输出当前方法被调用的调用路径
Jad:反编译指定已加载类的源码,反编译便于理解业务
logger:查看和修改 logger,可以动态更新日志级别
Arthas支持使用管道对上述命令的结果进行进一步的处理,如 sm java.lang.String * | grep 'index'
grep——搜索满足条件的结果
plaintext——将命令的结果去除ANSI颜色
wc——按行统计输出结果
后台异步任务:
当线上出现偶发的问题,比如需要watch某个条件,而这个条件一天可能才会出现一次时,异步后台任务就派上用场了,详情请参考
这里
使用 > 将结果重写向到日志文件,使用 & 指定命令是后台运行,session断开不影响任务执行(生命周期默认为1天)
jobs——列出所有job
kill——强制终止任务
fg——将暂停的任务拉到前台执行
bg——将暂停的任务放到后台执行
用户数据回报:
- 在 3.1.4 版本后,增加了用户数据回报功能,方便统一做安全或者历史数据统计。
- 在启动时,指定 stat-url ,就会回报执行的每一行命令,比如: ./as.sh --stat-url
- 'http://192.168.10.11:8080/api/stat’
- 在tunnel server里有一个示例的回报代码,用户可以自己在服务器上实现
- 这里只列出常用命令,完整列表参考命令列表:https://github.com/alibaba/arthas/blob/master/README_CN.md
案例 查看 dashboard
输入dashboard,按 回车/enter ,会展示当前进程的信息,按 ctrl+c 可以中断执行。
案例 thread -1 查看线程信息
通过thread命令来获取到应用进程的线程信息,thread -1 会打印线程统计信息
案例 jad 反编译项目代码
命令 jad com.anmed.jjld.JjldApplication
案例 watch 监听方法返回值
通过watch命令来查看 com.hero.web.user.controller#UserController 函数的返回值
命令:watch com.hero.web.user.controller.UserController findByUsername returnObj
退出 Arthas
- 如果只是退出当前的连接,可以用 quit 或者 exit 命令。Attach到目标进程上的arthas还会继续运行,端口会保持开放,下次连接时
- 可以直接连接上。如果想完全退出arthas,可以执行 stop 命令
Java 生成堆快照的方式
- 使用 jmap -dump 可以在 JVM 运行时获取 dump
- 使用 jconsole 选项通过 HotSpotDiagnosticMXBean 从运行时获得堆转储。
- 使用 VisualVM 也可以直接获得堆存储
- 应用启动时增加虚拟机参数,-XX:+HeapDumpOnOutOfMemoryError 抛出 OOM 时,会自动会自动执行堆转储。
Java 项目保存 GC 日志的 vm 参数
如果想要确定JVM性能问题瓶颈,需要分析GC日志
- -XX:+PrintGCDetails 开启GC日志创建更详细的GC日志,默认关闭
- -XX:+PrintGCTimeStamps,-XX:+PrintGCDateStamps 开启GC时间提示,开启时间便于我们更精确地判断几次GC操作之间的时两个参数的区别时间戳是相对于0(依据JVM启动的时间)的值,而日期戳(date stamp)是实际的日期字符串由于日期戳需要进行格式化,所以它的效率可能会受轻微的影响,不过这种操作并不频繁,它造成的影响也很难被我们感知。
- -XX:+PrintHeapAtGC 打印堆的GC日志
- -Xloggc:./logs/gc.log 指定GC日志路径
Idea 配置案例:-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:C:\Users\StoneYu\Desktop\临时文件\gc-default.log -XX:+PrintCommandLineFlags