JVM(九)性能监控与调优
背景说明
为什么要调优
- 防止出现OOM
- 解决OOM
- 减少Full GC出现的频率
监控的依据
- 运行日志
- 异常的堆栈信息
- GC日志
- 线程快照
- 堆转储快照
性能优化步骤
性能监控(发现问题)
一种以非强行或者入侵凡是收集或查看应用运行性能数据的活动。监控通常是指一种在生产、质量评估或者开发环境下实时的带有预防或主动性的活动。当应用相关干系人提出性能问题却没有提供足够多的线索是,首先我们需要进行性能监控,随后是性能分析。
- GC频繁
- CPU load 过高
- OOM
- 内存泄露
- 死锁
- 程序响应时间过长
性能分析(排查问题)
一种以侵入方式收集运行性能数据的活动,它会影响应用的吞吐量或响应性。性能分析是针对性能问题的答复结果,关注的范围通常比性能监控更加集中。性能分析很少在生产环境下进行,通常是在质量评估、系统测试或者开发环境下进行,是性能监控之后的步骤。
- 打印GC日志,通过GCViewer或者GCeasy来分析日志信息
- 灵活运用命令行工具,jstack,jmap,jinfo等
- dump出推文件,使用内存分析工具分析文件
- 使用阿里Arthas,或jconsole,JVisualVM来实时查看JVM状态
- jstack查看堆栈信息
性能调优(解决问题)
一种为改善应用响应性或吞吐量而更改参数、源代码、属性配置的活动,性能调优是在性能监控、性能分析之后的活动。
- 适当增加内存,根据业务背景选择垃圾回收器
- 优化代码,控制内存使用
- 增加机器,分散节点压力
- 合理设置线程池线程数量
- 使用中间件提高程序效率,比如缓存、消息队列等。
- 其它…
性能评价/测试指标
- 停顿时间(响应时间):①提交请求和返回请求的响应之间使用的时间,一般比较关注平均响应时间。
②在垃圾回收环节中:暂停时间是执行垃圾收集时,程序的工作线程被暂停的时间。-XX:MaxGCPauseMillis - 吞吐量:①对单位时间内完成的工作量(请求)的量度。②在GC中运行用户代码的时间站总运行时间的比例,(总运行时间=程序的运行时间+内存回收的时间)
- 并发数:同一时刻,对服务器有实际交互的请求数
- 内存占用:java堆区所占得内存大小
JVM监控工具-命令行
Java作为最流行的编程语言之一,其应用性能诊断一直收到业界广泛关注。可能造成Java应用出现性能问题的因素非常多,例如线程控制、磁盘读写、数据库访问、网络I/O、垃圾收集等。想要定位这些问题,一款优秀的性能诊断工具必不可少。
jps(Java Process Status)
查看正在运行的java进程
语法
jps [options] [hostid]
参数
- options
-q:只显示本地虚拟机唯一id,不显示主类的名称等
-l:输出应用程序主类的全类名 或执行的是jar包则输出jar完整路径
-m:输出虚拟机进程启动时传递给主类的main()的参数
-v:列出虚拟机进程启动时的JVM参数。比如:-Xms -Xmx等
- hostid
RMI注册表中注册的主机名。如果想要远程监控主机上的java程序,需要安装jstatd。
对于具有更严格的安全实践的网络场所而言,可能使用一个自定义的策略文件来显示对特定的可信主机或网络的访问,尽管这种技术容易收到IP地址欺诈攻击
如果安全文件无法使用一个定制的策略文件来处理,那么最安全的操作时不运行jstatd服务器,而是在本地使用jstat和jps工具。
jstat(JVM Statistics Monitoring Tool)
查看JVM统计信息,用于监视虚拟机各种运行状态信息的命令行工具。他可以显示本地或者远程虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据。
在没有GUI图形界面,只提供了春文件控制台环境的服务器上,他将是运行期定位虚拟机性能问题的首选工具。常用于检测垃圾回收问题以及内存泄露问题。
jstat还可以用来判断是否出现内存泄露。
- 在长时间运行的java程序中,我们可以运行jstat命令连续获取多行性能数据,并取这几行数据中OU列(即已占用的老年代内存)的最小值。
- 然后,我们每隔一段较长的时间重复一次上述操作,来获得多组OU的最小值。如果这些值呈上涨趋势,则说明该java程序的老年代内存已使用量在不断上涨,这意味着无法回收的对象在不断增加,因此很有可能存在内存泄露。
语法
jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]
参数
- option
-class:显示ClassLoader的相关信息,类的装载、卸载数量、总空间、类装载所消耗的时间等。
[root@hadoop01 ~]# jstat -class 58996
Loaded Bytes Unloaded Bytes Time
7136 15249.2 9 10.5 3.28
-gc:显示与GC先关的堆信息。包括Eden区、两个Survivor区、老年代、永久代等的容量、已用空间、GC时间合计等信息。
[root@hadoop01 ~]# jstat -gc 58996
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
0.0 3072.0 0.0 3072.0 52224.0 5120.0 993280.0 146406.0 51656.0 45949.6 6856.0 6189.1 39 1.311 0 0.000 1.311
-gccapacity:显示内容与-gc基本相同,但输出主要关注java堆各个区域使用到的最大、最小空间
[root@hadoop01 ~]# jstat -gccapacity 58996
NGCMN NGCMX NGC S0C S1C EC OGCMN OGCMX OGC OC MCMN MCMX MC CCSMN CCSMX CCSC YGC FGC
0.0 1048576.0 55296.0 0.0 3072.0 52224.0 0.0 1048576.0 993280.0 993280.0 0.0 1093632.0 51656.0 0.0 1048576.0 6856.0 39 0
-gcutil:显示内容与-gc基本相同,但输出主要关注已使用空间站总空间的百分比。
[root@hadoop01 ~]# jstat -gcutil 58996
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
0.00 100.00 54.90 14.74 88.95 90.27 39 1.311 0 0.000 1.311
-gccause:与 -gcutil功能一样,但是会额外输出导致最后一次或当前正在发生的GC产生的原因。
[root@hadoop01 ~]# jstat -gccause 58996
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT LGCC GCC
0.00 100.00 56.86 14.74 88.95 90.27 39 1.311 0 0.000 1.311 G1 Evacuation Pause No GC
-gcnew:显示新生代GC状况。
[root@hadoop01 ~]# jstat -gcnew 58996
S0C S1C S0U S1U TT MTT DSS EC EU YGC YGCT
0.0 3072.0 0.0 3072.0 15 15 3584.0 52224.0 30720.0 39 1.311
-gcnewcapacity:显示内容与-gcnew基本相同,输出主要关注使用到的最大、最小空间。
[root@hadoop01 ~]# jstat -gcnewcapacity 58996
NGCMN NGCMX NGC S0CMX S0C S1CMX S1C ECMX EC YGC FGC
0.0 1048576.0 55296.0 0.0 0.0 1048576.0 3072.0 1048576.0 52224.0 39 0
-gcold:显示老年代GC状况
[root@hadoop01 ~]# jstat -gcold 58996
MC MU CCSC CCSU OC OU YGC FGC FGCT GCT
51656.0 45949.6 6856.0 6189.1 993280.0 146406.0 39 0 0.000 1.311
- interval
用于指定输出统计数据的周期,单位为毫秒。即:查询间隔
[root@hadoop01 ~]# jstat -class 58996 1000
Loaded Bytes Unloaded Bytes Time
7136 15249.2 9 10.5 3.28
7136 15249.2 9 10.5 3.28
7136 15249.2 9 10.5 3.28
7136 15249.2 9 10.5 3.28
7136 15249.2 9 10.5 3.28
7136 15249.2 9 10.5 3.28
7136 15249.2 9 10.5 3.28
- count
用于指定查询的总次数
[root@hadoop01 ~]# jstat -class 58996 1000 5
Loaded Bytes Unloaded Bytes Time
7136 15249.2 9 10.5 3.28
7136 15249.2 9 10.5 3.28
7136 15249.2 9 10.5 3.28
7136 15249.2 9 10.5 3.28
7136 15249.2 9 10.5 3.28
[root@hadoop01 ~]#
- -t
可以在输出信息前加上一个Timestamp列,显示程序的运行时间,单位,秒。
我们可以比较java进程的启动时间以及总GC时间(GCT列),或者两次测量的间隔时间以及总GC时间的增量,来得出GC时间占总运行时间的比例。
如果该比例超过20%,则说明目前堆空间的压力较大;
如果该比例超过90%,则说明堆里几乎没有可用空间,随时都可能抛出OOM异常。
[root@hadoop01 ~]# jstat -class -t 58996 1000 5
Timestamp Loaded Bytes Unloaded Bytes Time
64975.3 7136 15249.2 9 10.5 3.28
64976.4 7136 15249.2 9 10.5 3.28
64977.4 7136 15249.2 9 10.5 3.28
64978.3 7136 15249.2 9 10.5 3.28
64979.4 7136 15249.2 9 10.5 3.28
- -h
可以在周期性数据输出时,输出多少行数据后输出一个表头信息
[root@hadoop01 ~]# jstat -class -t -h3 58996 1000 5
Timestamp Loaded Bytes Unloaded Bytes Time
65023.3 7136 15249.2 9 10.5 3.28
65024.2 7136 15249.2 9 10.5 3.28
65025.3 7136 15249.2 9 10.5 3.28
Timestamp Loaded Bytes Unloaded Bytes Time
65026.3 7136 15249.2 9 10.5 3.28
65027.3 7136 15249.2 9 10.5 3.28
jinfo(Configuration Info for Java)
实时查看和修改JVM配置参数,查看虚拟机配置参数信息,也可以用于调整虚拟机的配置参数。
在很多情况下,java应用程序不会指定所有的java虚拟机参数。而此时,开发人员可能不知道某一个具体的java虚拟机参数的默认值。在这种情况下,可能需要通过查询文档获取某个参数的默认值。这个查找过程可能是非常艰难的。但有了jinfo工具,开发人员可以很方便的找到java虚拟机参数的当前值。
jinfo不仅可以查看某一个java虚拟机参数的实际取值,甚至可以在运行时修改部分参数,并使之立即生效。但是,并非所有参数都支持动态修改。参数只有被标记为manageable的flag可以被实时修改。其实这个修改能力是极其有限的。
语法
- to connect to running process
jinfo [option] <pid>
- to connect to a core file
jinfo [option] <executable <core>
- to connect to remote debug server
jinfo [option] [server_id@]<remote server IP or hostname>
参数
- option
no option:输出全部的参数和系统属性。
-flag name:输出对应名称的参数。
-flags:输出全部的参数
-sysprops:输出系统的参数
-flag [±] name:开启或者关闭对应名称的参数,只有被标记为manageable的参数才可以被动态修改。(java -XX:+PrintFlagsFinal -version | grep manageable)
-flag name=value:设定对应名称的参数。
jmap(JVM Memory Map)
导出内存映像文件&内存使用情况。获取dump文件(推转储快照文件,二进制文件,指一个Java进程在某个时间点的内存快照),它还可以获取目标java进程的内存相关信息,包括java堆各区域的使用情况、堆中对象的统计信息、类加载信息等。
- to connect to running process
jmap [option] <pid>
- to connect to a core file
jmap [option] <executable <core>
- to connect to remote debug server
jmap [option] [server_id@]<remote server IP or hostname>
参数
- option
-dump:生成Java堆转储快照dump文件,-dump:live只保存堆中的存活对象
-heap:输出整个堆空间的详细信息,包括GC的使用、堆配置信息,以及内存的使用信息等。
-histo:输出推空间中对象的统计信息,包括类、实力数量和合计容量。-histo:live只统计堆中的存活对象
-finalizerinfo:显示在F-Queue中等待Finalizer线程执行fianlize方法的对象。
-permstat:以ClassLoader为统计口径输出永久代的内存状态信息。
-F:当虚拟机进程堆-dump选项没有任何响应时,强制执行生成dump文件。
导出内存映像文件
- 手动方式
jmap -dump:format=b,file=<filename.hprof> <pid.>
jmap -dump:live,format=b,file=<filename.hprof> <pid.>
- 自动方式
-XX:+HeapDumpOnOutOfMemoryError:在程序发生OOM时,导出应用程序的当前堆快照。
-XX:HeapDumpPath=<filename.hprof>:可以指定堆快照的保存位置
jhat(JVM Heap Analysis Tool)
Sun JDK 提供的jhat 命令与jmap命令搭配使用,用于分析jmap生成的heap dump 文件(堆转储快照),jhat 内置了一个微型的http/html服务器,生成dump文件的分析结果后,用户可以在浏览器中查看分析结果。
使用了jhat命令,就启动了一个http服务,端口是7000,即http://localhost:7000/。
jhat命令在JDK9、JDK10中已经被删除,官方建议用VisualVM代替
jhat [-stack <bool>] [-refs <bool>] [-port <port>] [-baseline <file>] [-debug <int>] [-version] [-h|-help] <file>
jstack(JVM Stack Trace)
用于生成虚拟机指定进程当前时刻的线程快照(虚拟机堆栈跟踪)。线程快照就是当前虚拟机内指定进程的每一条线程正在执行的方法堆栈的集合。
生成线程快照的作用:可用于定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等问题。这些都是导致线程长时间停顿的常见原因。当线程出现停顿时,就可以用jstack显示各个线程调用的堆栈情况。
在Thread dump中,要留意下面几种状态:
- 死锁,Deadlock(重点关注)
- 等待资源,Waiting on condition(重点关注)
- 等待获取监视器,Waiting on monitor entry(重点关注)
- 阻塞,Blocked(重点关注)
- 执行中,Runnable
- 暂停,Suspended
语法
jstack option pid
参数
-F:当正常输出的情趣不被响应时,强制输出线程堆栈
-l:除堆栈外,显示关于锁的附加信息
-m:如果调用到本地方法的话,可以显示C/C++的堆栈
-h:帮助操作
在java代码层面使用如下方式也可打印线程的堆栈信息
Map<Thread, StackTraceElement[]> all = Thread.getAllStackTraces();
Set<Map.Entry<Thread, StackTraceElement[]>> entries = all.entrySet();
for (Map.Entry<Thread, StackTraceElement[]> en : entries) {
Thread t = en.getKey();
StackTraceElement[] v = en.getValue();
log.info("【Thread name is {}:】", t.getName());
for (StackTraceElement s : v) {
log.info(s.toString());
}
}
jcmd
多功能命令行,在JDK1.7以后,新增了一个命令行工具jcmd。他是一个多功能的工具,可以用来实现前面除了jstat之外所有命令的功能。比如:用它来导出堆、内存使用、查看Java进程、导出线程信息、执行GC、JVM运行时间等。
jcmd拥有jmap的大部分功能,并且在Oracle的官方网站上也推荐使用jcmd命令代jmap命令
语法
jcmd <option>
参数
- option
jcmd -l:列出所有的JVM进程
[chao@hadoop01 logs]$ jcmd
3031 kafka.Kafka /opt/module/kafka/config/server.properties
3389 sun.tools.jcmd.JCmd
jcmd pid help:针对指定的进程,列出支持的所有命令
[chao@hadoop01 logs]$ jcmd 3031 help
3031:
The following commands are available:
JFR.stop
JFR.start
JFR.dump
JFR.check
VM.native_memory
VM.check_commercial_features
VM.unlock_commercial_features
ManagementAgent.stop
ManagementAgent.start_local
ManagementAgent.start
VM.classloader_stats
GC.rotate_log
Thread.print
GC.class_stats
GC.class_histogram
GC.heap_dump
GC.finalizer_info
GC.heap_info
GC.run_finalization
GC.run
VM.uptime
VM.dynlibs
VM.flags
VM.system_properties
VM.command_line
VM.version
help
For more information about a specific command use 'help <command>'.
jcmd pid 具体命令:显示指定进程的指令命令的数据
jstatd
远程主机信息收集。之前的指令只涉及到监控本机的Java应用程序,而在这些工具中,一些监控工具也支持对远程计算机的监控(如jps、jstat)。为了启用远程监控,则需要配合使用jstatd工具。
命令jstatd是一个RMI服务端程序,它的作用相当于代理服务器,建立本地计算机与远程监控工具的通信。jstatd服务器将本机的Java应用程序信息传递到远程计算机。
JVM监控工具-GUI工具
使用上述命令行工具或组合能帮你获取目标Java应用性能相关的基础信息,但他们存在一下局限:
- 无法获取方法级别的分析数据,如方法间的调用关系、各方法的调用次数和调用时间等(这对定位应用性能瓶颈至关重要)。
- 要求用户登录到目标Java应用所在的宿主机上,使用起来不是很方便。
- 分析数据通过终端输出,结果展示不够直观。
为此,JDK提供了一些内存泄露的分析工具,如jconsole,jvisualvm等,用于辅助开发人员定位问题。
JDK自带的工具
- jconsole:JDK自带的可视化监控工具。查看Java应用程序的运行概况、监控堆信息、永久区(或元空间)使用情况、类加载情况等。JAVA_HOME\bin\jconsole.exe。
- Visual VM:提供了一个可视界面,用于查看Java虚拟机上运行的基于Java技术的应用程序的详细信息。JAVA_HOME\bin\jvisualvm.exe
- JMC:Java Mission Control,内置Java Flight Recorder。能够以极低的性能开销收集Java虚拟机的性能数据。
第三方工具
- MAT:Memory Analyzer Tool是基于Eclipse的内存分析工具,是一个快速、功能丰富的Java heap分析工具,他可以帮助我们查找内存泄露和减少内存消耗。Eclipse的插件形式。
- JProfiler:商业软件,需要付费。功能强大。
- Arthas:阿尔萨斯,Alibaba开源的Java诊断工具。https://github.com/alibaba/arthas/releases
- Btrace:Java运行时追踪工具。可以在不停机的情况下,跟踪指定的方法调用、构造函数调用和系统内存等信息。
工具概述
Visual VM
概述
- Visual VM 是一个功能强大的多合一故障诊断和性能监控的可视化工具。
- 他集成了多个JDK命令行工具,使用Visual VM可用于显示虚拟机进程及进程的配置和环境信息(jps,jinfo),监视应用程序的CPU、GC、堆、方法区及线程的信息(jstat、jstack)等,甚至代替jconsole。
- 在JDK 6 Update 7 以后,Visual VM便作为JDK的一部分发布(VisualVM 在JDK/bin目录下)
- 此外Visual VM也可以作为独立的软件安装。
链接方式
- 远程链接
1.确定远程服务器的IP地址。
2.添加JMX(通过JMX技术具体监控远端服务器哪个Java进程)
3.修改bin/catalina.sh文件,链接远程的tomcat
4.在…/conf中添加jmxremote.access和jmxremote.password文件
5.将服务器地址改为公网ip地址
6.设置阿里云安全策略和防火墙策略
7.启动Tomcat,查看Tomcat启动日志和端口监听
8.JMX中输入端口号、用户名、密码登录
主要功能
- 生成、读取堆内存快照
- 查看JVM参数和系统属性
- 查看运行中的虚拟机进程
- 生成、读取线程快照
- 程序资源的实时监控
- 其它功能(JMX代理连接、远程环境监控、CPU分析和内存分析)
Arthas
阿尔萨斯是阿里巴巴开源的Java诊断工具,在线排查问题,无需重启;动态跟踪Java代码;实时监控JVM状态。
支持JDK 6+,支持Linux/max/windows,采用命令行交互模式,同时提供丰富的Tab自动补全功能,进一步方便进行问题的定位和诊断。
安装
https://arthas.aliyun.com/doc/
- 在/usr/local/bin目录下执行
[root@hadoop01 bin]# curl -L https://arthas.aliyun.com/install.sh | sh
- 执行as.sh
[root@hadoop01 bin]# as.sh
Arthas script version: 3.5.2
[INFO] JAVA_HOME: /opt/module/jdk1.8.0_261
[INFO] Process 58996 already using port 3658
[INFO] Process 58996 already using port 8563
Found existing java process, please choose one and input the serial number of the process, eg : 1. Then hit ENTER.
* [1]: 58996 kafka.Kafka
[2]: 3992 org.apache.zookeeper.server.quorum.QuorumPeerMain
- 选择需要监控的进程序号[1]
Attach success.
telnet connecting to arthas server... current timestamp is 1626938671
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
,---. ,------. ,--------.,--. ,--. ,---. ,---.
/ O \ | .--. ''--. .--'| '--' | / O \ ' .-'
| .-. || '--'.' | | | .--. || .-. |`. `-.
| | | || |\ \ | | | | | || | | |.-' |
`--' `--'`--' '--' `--' `--' `--'`--' `--'`-----'
wiki https://arthas.aliyun.com/doc
tutorials https://arthas.aliyun.com/doc/arthas-tutorials.html
version 3.5.2
main_class
pid 58996
time 2021-07-22 15:22:49
[arthas@58996]$
- 退出当前客户端:quit/exit
- 关闭arthas服务端,并退出所有客户端:stop/shutdown
基础指令
常用操作命令详见官方文档https://arthas.aliyun.com/doc/