监控调优
调优步骤
性能监控
一种非侵入
方式收集或者查看应用运营性能数据的活动.监控通常是一种在生产、质量评估或者开发环境下实施的带有预防或者主动性的活动.当应用的出现性能问题却没有足够多的线索时,首先需要进行性能监控
如GC频繁、cpu负载过高、OOM、内存泄漏、死锁、应用响应时间过长
性能分析
一种侵入
方式收集的运行性能数据,它可能会影响到应用的吞吐量或响应性.性能分析是针对性能问题的答复结果,关注点比性能监控更集中.性能分析很少在生产环境下进行,通常是在质量评估、系统测试或者开发环境下进行,是性能监控之后的步骤
性能调优
一种改善
应用响应性或吞吐量而更改参数]源代码、属性配置,性能调优是在性能监控和性能分析之后的步骤
监控与诊断工具
命令行
以下命令实例中中,[]
中包含的参数代表代表可选参数,<>
中包含的参数代表必选参数
JPS
Java Process Status 显示当前操作系统中所有的HotSpot虚拟机的进程信息
-q 仅显示JVM的pid
-l 输出应用程序的全类名,如果是jar包则输出完整路径
-m 输出jvm启动时传递给main方法的参数
-v 列出jvm启动时的jvm参数
只显示jvm相关进程,和ps命令类似
如果关闭了默认开启的性能监控(-XX:-UsePerfData),则jps无法检测到对应的jvm
JSTAT
JVM Statistics Monitoring Tool 用于监视虚拟机的各种运行状态信息的命令行工具,可以显示本地或远程虚拟机的类装载、内存、垃圾收集、JIT编译等数据
jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]
option 查询参数
类装载相关
-class 显示ClassLoader相关信息
类的装载、类的装载总量、类的卸载、类的卸载总量、类装载器消耗时间
JIT相关
-compiler 显示JIT编译器编译过的方法、耗时等信息
-printcompilation 输出已经被JIT编译的方法
垃圾收集相关
-gc 显示gc相关的堆信息
-gccapacity 和
-gc
类似,关注堆的使用到的最大最小空间-gcutil 和
-gc
类似,关注占用堆空间百分比-gccause 和
-gcutil
类似,关注垃圾回收事件的原因-gcnew 新生代GC状况
-gcnewcapacity 新生代内存统计
-gcold 老年代gc状况
-gcoldcapacity 老年代内存统计
-gcmetacapacity 方法区内存统计
# 查看指定端口号jvm的类装载信息
jstat -class 37763
$ Loaded Bytes Unloaded Bytes Time
756 1791.6 0 0.0 0.03
-t 进程运行的时间(秒)
Timestamp GCT # GC时间占比0.176%
2990.6 76.601
3008.7 76.633
可以通过两次的进程运行的时间和GC时间的增量来计算GC时间占运行时间的比例
如果该比例超过20%则说明堆压力较大,如果比例超过90%则说明堆几乎没有可用空间随时会导致OOM
Timestamp OU
2989.6 240535.2
2995.6 240951.2
2998.6 241095.2
3001.6 241247.2
3004.6 241711.3
3007.7 243269.0
运行jstat命令连续获取多行性能数据,并取这几行数据中OU(即已占用的老年代内存)的最小值
每隔一段较长的时间重复一次上述操作,来获得多组OU最小值.如果这些值呈上涨趋势,则说明JVM老年代内存已使用量在不断上涨,也就是无法回收的对象在不断增加,因此很有可能存在内存泄漏
-h 每隔指定次数输出一次表头
vmid jvm的pid
interval 查询间隔(毫秒)
count 查询次数
# 查看指定端口号jvm的类装载信息
# 并显示运行时间戳,设置每秒打印一次,一共打印50次,每打印3次打印一次表头
jstat -class -t -h 3 37763 1000 50
JINFO
Configuration Info for Java 查看虚拟机参数配置信息,也可以用于调整虚拟机参数
可以通过jinfo命令查看某个虚拟机参数的默认值
jinfo <option> <pid>
-sysprops 查看指定jvm的完整虚拟机参数
和System.getProperties()取得的参数一致
jinfo -sysprops 43560
-flags查看所有JVM曾赋值过的参数
jinfo -flags 46681
-flag 查看指定参数
jinfo -flag UseParallelGC 46681
$ -XX:+UseParallelGC
不旦可以查看还能修改,并使之立刻生效.但不是所有参数都支持动态修改,只有被标记为manageable的参数可以被实时修改
可以通过改命令查看可以被修改的参数
java -XX:+PrintFlagsFinal -version | grep manageable
开启指定JVM参数
布尔类型
jinfo -flag PrintGCDetails 47624
$ -XX:-PrintGCDetails # 未开启
jinfo -flag +PrintGCDetails 47624
jinfo -flag PrintGCDetails 47624
$ -XX:+PrintGCDetails # 已开启
数值类型
jinfo -flag MaxHeapFreeRatio=90 47624
JMAP
JVM Memory Map 获取JVM的dump文件,获取JVM进程的内存相关信息
由于jmap会访问堆中的所有对象,为了保证在此过程中不被应用现场干扰,jmap需要借助安全点的机制,让所有线程听伦在不改变堆中数据的状态.因此jmap导出的快照都是在安全点的位置,这也导致基于该快照的分析可能存在偏差,例如某个对象在两个安全点之间,那么可能无法探知到这些对象
jinfo <option> <pid>
-dump 生成堆转储快照
-dump:live 只保存堆中存活对象
实际使用一般更推荐使用-dump:live,因为它容量更小,更重要的是分析堆内存中的存活对象才是更重要的(只有存活对象才会不被回收)
jmap -dump:format=b,file=./Desktop/GCTest.hprof 60408
jmap -dump:live,format=b,file=./Desktop/GCTest4.hprof 60148
除了使用上述方式在生成环境中使用jmap方式生存dump文件,也可以使用在系统OOM时自动生成dump文件
使用场景不一样,自动生成可以第一时间保存导致OOM的快照信息
而使用jmap可以在系统监控异常但是还没有OOM的时候生成快照信息
-XX:+HeapDumpOnOutOfMemoryErro
-XX:+HeapDumpPath=./dump.hprof
-heap 生成目前整个堆空间的详细信息
部分命令jmap已经不直接支持,需要使用jhsdb
jhsdb jmap --heap --pid 3255
-histo 生成目前整个堆空间对象的统计信息
jmap -histo 13627
-F 当-dump没有响应时,添加此参数强制生成
-J 传递参数给jmap启动的jvm
JHAT
JVM Heap Anaysis Tool,jdk提供的dump文件分析工具,不过在jdk9之后被删除,官方建议直接使用VisualVM
通过以下命令可以分析指定dump文件,分析完毕以后可以通过127.0.0.1:7000访问简易界面查看
jhat dump.hprof
-J 传递参数给jmap启动的jvm
指定jhat的堆内存大小
jhat -J-mx20g dump.hprof
-port 指定http端口,默认7000
JSTACK
JVM Stack Trace 生成虚拟机当前时刻的线程快照.线程快照就是当前虚拟机内每一条线程正在 执行的方法堆栈快照
线程快照可以定位线程出现长时间停顿的原因
jstack [-l] [-e] <pid>
-l 显示锁的附加信息
Locked ownable synchronizers:
- None
执行jatack打印所有线程状态
如果有死锁会在Found one Java-level deadlock中展示
jstack 41931
死锁 Deadlock*
等待资源 Waiting on condition*
等待获取监视器 Waiting on monitor entry*
阻塞 Blocked*
执行中 Runnable
暂停 Suspended
对象等待中 TIMED_WAITING
停止 Parked
jstack类似以下代码
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[] value = en.getValue();
System.out.println("[Thread name" + t.getName() + "]");
for (StackTraceElement element : value) {
System.out.println("\t" + element.toString());
}
}
JCMD
命令行工具jcmd是一个多功能的工具可以用来实现前面除了jstat之外大部分命令的功能
针对指定进程查看所有支持的命令
jcmd 44930 help
以下指令类似jps -m,查看所有jvm相关进程详细信息
jcmd -l
以下命令类似jstack
jcmd 44930 Thread.print
以下命令类似jmap -histo
jcmd 44930 GC.class_histogram
以下命令类似jmap -dump
jcmd 44930 GC.heap_dump /./Desktop/cmd_dump.hpro
以下命令,让JVM执行GC
jcmd 44930 GC.run
以下命令类似jstat -gc,查看gc执行时间
jcmd 44930 VM.uptime
以下命令类似jinfo -sysprops,查看虚拟机参数
jcmd 44930 VM.system_properties
以下命令类似jinfo -flags
jcmd 45273 VM.flags
JSTATD
以上命令只能监控本机jvm程序,为了启用远程监控,则需要配合使用statd
图形化界面
使用命令行工具虽然能获取jvm相关信息,但是他们存在一些局限
无法获取方法级别的分析数据,如方法间的调用关系、各方法的调用次数和调用时间等
需要登录到宿主机上
通过终端输出结果展示不够直观
jconsole
在JDK中自带的java监控和管理控制台.用于对JVM中内存、线程和类等的监控,是一个基于JMX的GUI性能监控工具
打开jconsole
jconsole
Visual VM
Visual VM是一个功能强大的多合一故障诊断和性能监控可视化工具,集成了多个jdk命令行工具,可以显示虚拟机进程,及进程的配置和环境信息,监视应用程序的cpu、gc、堆、方法去及线程的信息
visual vm支持插件,可以在此浏览链接,可以直接通过客户端的Tools-Plugins,在Available Plugins中选择自己需要的插件
生成dump文件
可以通过右键Heap Dump生成,或者点击Monitor右侧Heap Dump按钮生成
保存dump文件
读取dump文件
可以通过Select Heap Dump to Compare多个dump文件或者本地服务进行对比
监控线程
抽样器
查看线程的占用情况
MAT
MAT是一款功能强大的dump文件分析器,生成内存泄漏报表,方便定位并分析问题.可以用于查找内存泄漏以及查看内存消耗情况,目前支持主流的hprof和phd格式的文件
导入或着实时监控
通过Acquire Heap Dump获得指定jvm的dump文件
Open Heap Dump直接分析保存的dump文件
生成报告
每次打开dump文件都会弹出一下向导,也可以在总览下面找到
分别是 内存泄漏疑点报告,组件报告,重新打开已经运行的报告
泄漏疑点报告 Leak Suspects
问题1 该线程所存储的局部变量占据了71.9%,内部有Object数组被cystem class loader加载占用71.9%
直方图 Histogram
右键任意Class Name, Columns-Sort By排序
右键指定Class,Merge Shortest Paths to GC Roots-exclude all phantom/weak/soft etc. references ,该Class的非虚引用软引用的引用情况
点击最右侧按钮Select Baseline,实现多个dump进行对比
点击顶部<regex>正则表达式匹配
其中Shallow Heap 浅堆(该对象消耗的内存)
浅堆指对象本身占用的内存,不包括内部引用对象的大小
Retained Heap 深堆(该对象保留集中所有对象的浅堆之和)
对象的保留集指的是该对象被回收后,可以被回收的所有对象集合(包括该对象本身),即对象的保留集可以被认为是
只能通过
该对象直接或间接访问的所有对象的集合,也就是仅
被该对象持有的所有对象集合
以下对象显示了一个简单的引用关系,对象A、D的浅堆大小仅包含本身
A的深堆大小是A、C,D的深堆大小为D、E
因为B被两个对象引用,所以无法计算在深堆内
线程概述 thread overview
点击第四个按钮查看线程概述
其中包含<local>的代表是局部变量,有内存泄漏的可能,<class>代表该Class所引用到的Class
可以右键任意Object/Stack Fame,List objects查看对象输入(被关联)和输出(关联)的引用对象(引用链),查看是否被其他对象应用导致内存泄漏
elementData代表所关联到的实例(ArrayList内实际存储数据的数组)
支配树 dominator_tree
支配树体现了对象实例之间的支配关系,在对象引用图中,所有对象都指向对象B路径都经过了对象A,则认为对象A支配了对象B.若对象A是离对象B最近的一个支配对象,则认为对象A是对象B的直接支配者
Arthas
以上的一些工具都需要在服务端进行相关的配置,然后才能通过远程连接到项目进程获取相关数据.然而线上的环境大多数情况下都是隔离的
Arthas可以在线排除问题无需重启、动态追踪代码,实时监控jvm状态
使用,也可以直接在后面添加jvm的pid,当显示arthas的logo时说明开启成功
java -jar arthas-boot.jar
[INFO] JAVA_HOME: /Library/Java/JavaVirtualMachines/zulu-11.jdk/Contents/Home
[INFO] arthas-boot version: 3.7.2
[INFO] Process 90216 already using port 3658
[INFO] Process 90216 already using port 8563
成功后也可以选择web界面输入命令
127.0.0.1:3658
如果arthas没有正确退出,重新打开需要连接上一次监听的jvm,然后执行stop指令之后,再监听新的jvm进程
否则会出现异常
查看日志
cat ~/logs/arthas/arthas.log
tail -f ~/logs/arthas/arthas.log
退出 exit退出当前客户端,stop退出arthas服务端并退出所有客户端
查看帮助文档
java -jar arthas-boot.jar -h
arthas中的基础命令和linux中的类似具体参考文档
或者在命令后面追加-h
dashboard 查看jvm的概括信息
# 每0.5秒打印一次,累计4次
dashboard -i 500 -n 4
thread 获取jvm的所有线程
# 指定线程id
thread 1
# block状态的线程
thread -b
# 每0.5秒打印一次,累计4次
thread -i 500 -n 4
sysprops jvm的属性信息
类似jinfo
sysenv jvm的环境变量
headdump 导出当前jvm的堆转储文件
heapdump ./dump.hprof
# 指定存活的对象
heapdump --live ./dump.hprof
sc 查看已加载的类信息
# 指定包下的类列表
sc java.*
# 该类的详细信息
sc -d java.lang.String
# 并且追加成员变量信息
sc -f -d java.lang.String
sm 查看方法
# 查看类所有方法
sm java.lang.String
# 查看指定方法
sm java.lang.String toString
# 查看方法详细信息
sm -d java.lang.String
jad 反编译代码
# 反编译指定类
jad java.lang.String
# 反编译指定类下的指定方法
jad java.lang.String toString
mc 编译代码
mc ./Desktop/Test.java
redefine 热更新代码
将mc命令编译的代码执行redefine
retransform 热更新代码
本地编译好的代码通过retransform热更新
classloader 类加载器相关信息
# 以树形结构展示类加载器
classloader -t
# 获取类加载器加载类的实例和hash
classloader -l
# 获取指定hash的类加载器的加载情况
classloader -c 764c12b6
monitor 方法执行监控
返回时间戳、类名、方法名、调用次数、成功次数、失败次数、平均 RT、失败率
# 每5秒显示一次指定类 构造方法执行情况
monitor -c 5 java.util.ArrayList <init>
# 每5秒显示一次指定类 指定方法的执行情况
monitor -c 5 java.util.ArrayList add
watch 观察方法的调用情况
查看方法的调用情况,返回值、抛出异常、入参
# 查看指定类的方法的入参和返回值,结果遍历深度2
watch java.util.ArrayList add "{params, returnObj}" -x 2
trace 该方法调用路径,并输出该方法中每个方法的耗时情况
trace java.util.ArrayList add
stack 输出方法的完整调用路径
stack java.util.ArrayList add
tt 记录同一个方法不同参数传入后的调用时间
tt传递的参数只是引用,如果参数经过后续处理,导致-p可能无法传递准确结果
# 记录
tt -t java.util.ArrayList add
# 查询记录列表
tt -l
# 筛选
tt -s 'method.name=="query"'
# 查看index调用信息
tt -i 1000
# 传递和index一样的参数,再次调用
tt -i 1000 -p
# 指定重载方法
tt -t *Test queryMemberDetailedDTOList params.length==1
tt -t *Test print 'params[1] instanceof Integer'
# 需要释放内存,否则会导致OOM
tt --delete-all
profiler 火焰图
# 启动profiler
profiler start
# 采集到的样本数量
profiler getSamples
# profiler状态
profiler status
# 停止采集并导出
# 之后可以通过http://localhost:3658/arthas-output/
profiler stop --format html
OQL
语法和sql类似
select查询可以指定字段,添加objects关键字可以将字符串解析为对象
from指定查询类
SELECT objects v.elementData FROM java.util.ArrayList v
查询ArrayList内所有对象的保留集
select as retained set * from java.util.ArrayList
查询指定地址的结构
如果是MAT可以通过Inspetor查看对象地址
用地址的好处是可以区分不同的类加载器加载的同一种类型
SELECT * FROM 0x6c00a9058
where添加过滤条件
select * from char[] s where s.@length > 10
select * from java.lang.String s where s.value != null
SELECT toString(f.path.value) FROM java.io.File f