JVM 调优
调优工具
jmap
jmap 是jdk自带的用来查看java程序内存信息的工具
pid 可通过 jps 获取
1⃣️ 查看Java对象实例信息:jmap -histo {pid} > {pid}.histo
num: 序号
instances: 实例数量
bytes: 内存占用大小
class name: 类名,如 [I 表示 int[],[B 表示 byte[],[C 表示 char[],[Ljava.lang.Object; 表示 Object[]
要读懂 class name 可参考这张图:
2⃣️ 查看堆配置和堆使用信息:jmap -heap {pid}
3⃣️ 导出堆快照信息:jmap -dump:format=b,file=xxx.hprof {pid}
导出hprof二进制文件后,可导入 jvisualvm 进行分析
设置下面的JVM参数可以在OOM时自动导出堆快照:
OOM时导出:-XX:+HeapDumpOnOutOfMemoryError
导出路径:-XX:HeapDumpPath={path}
jstack
查看应用线程信息:jstack {pid}
(可以帮助寻找死锁的线程)
jinfo
查看JVM参数:
查看Java系统属性:
jstat
通过jstat -options
可知jstat有下面这些功能:
-class // 显示类加载信息
-compiler // 显示编译信息
-gc // 显示GC相关信息
-gccapacity // 显示各区域容量及使用情况
-gccause // 显示垃圾回收信息
-gcmetacapacity // 显示元空间容量信息
-gcnew // 显示新生代信息
-gcnewcapacity // 显示新生代容量信息
-gcold // 显示老年代信息
-gcoldcapacity // 显示老年代容量信息
-gcutil // 显示垃圾收集信息
-printcompilation // 显示JIT编译的方法信息
查看堆内存使用情况和GC压力:jstat -gc {pid}
S0C:Survivor0 大小(KB)
S1C:Survivor1 大小(KB)
S0U:Survivor0 已使用大小(KB)
S1U:Survivor1 已使用大小(KB)
EC:Eden 大小(KB)
EU:Eden 已使用大小(KB)
OC:老年代大小(KB)
OU:老年代已使用大小(KB)
MC:元空间大小(KB)
MU:元空间已使用大小(KB)
CCSC:压缩类空间大小,跟指针压缩有关(KB)
CCSU:压缩类空间已使用大小(KB)
YGC:Young GC 总次数
YGCT:Young GC 总耗时(s)
FGC:Full GC 总次数
FGCT:Full GC 总耗时(s)
GCT:GC 总耗时(s)
jvisualvm
远程连接jvisualvm:(生产环境不允许使用)
配置服务器ip:`-Djava.rmi.server.hostname={ip}`
配置jmx端口:`-Dcom.sun.management.jmxremote.port={port}`
关闭jmx的ssl:`-Dcom.sun.management.jmxremote.ssl=false`
关闭jmx的认证:`-Dcom.sun.management.jmxremote.authenticate=false`
举例:`java -Djava.rmi.server.hostname=117.72.37.81 -Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port=8888 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -jar hello-1.0-SNAPSHOT.jar`
GC日志分析(GCEasy)
只要简单地将GC日志上传到 https://gceasy.io/ 网站,即可获取解析好的GC日志,AI也会提供优化建议。
GC日志相关参数:`-Xloggc:gc-%t.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:PrintGCCause -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCFileSize=100m`
Arthas
> 参考:https://arthas.aliyun.com/doc/quick-start.html
(1) 下载 Arthas:`curl -O https://arthas.aliyun.com/arthas-boot.jar`
(2) 运行Arthas:`java -jar arthas-boot.jar`
(3) 选择Java应用进程来attach:(如选 kafka 则输入 1 回车)
(4) 使用想要使用的命令:(如 dashboard、thread、jad 命令)
命令使用举例:
dashboard 查看总览
thread 查看线程栈
jad 反编译Java类
ognl 查看和修改线上实例属性
(5) 退出Arthas
退出当前连接:`quit` 或
exit
完全退出:`stop`
调优思路
(1) 年轻代对象增长速率
可以通过 jstat -gc {pid} {interval_ms} {times}
间隔一定时间多次调用,观察 EU 来估算 Eden 区每秒大概新增对象的大小。
注意,系统一般都有高峰期和日常期,需要分别估算增长速率。
(2) Young GC触发频率和平均耗时
知道了年轻代对象增长速率,就可以推算出Young GC大概多久触发一次
Young GC 平均耗时 = YGCT / YGC
(3) Young GC后有多少对象存活 & 有多少对象进入老年代
前面已经计算出了Young GC平均耗时,因此可以通过 jstat -gc {pid} {Young GC平均耗时} {times}
来观察 S0U/S1U 和 OU 变化情况。
S0U/S1U 和 OU 增长的对象就是存活的对象。
通过OU 可以估算老年代对象增长速率。
(4) Full GC触发频率和平均耗时
知道老年代对象增长速率,就可以推算出Full GC大概多久触发一次
Full GC 平均耗时 = FGCT / FGC
调优实战
CPU飙高
(1) 找到CPU占用高的进程:top
(2)查看占用CPU高的进程:top -p {pid}
(3)查看该进程所有线程的CPU占用:按H键
(4)将线程id转为16进制:printf '%x\n' {nid}
(5)查看线程堆栈信息:jstack {pid} | grep -A 10 {nid}
(6)最后根据堆栈信息定位问题
内存飙高
通过 jmap的histogram图排查,检查实例数多的类,寻找符合条件的类在哪些地方创建了大量实例
内存泄漏
不再使用或很少使用的数据没有及时清理,一直占用老年代空间,导致频繁的Full GC,这就是内存泄漏。
如JVM级缓存中一直加入数据,没有使用老化算法,导致内存泄漏。
频繁Full GC导致系统卡顿
逐一排查可能导致Full GC的场景:
元空间大小不足导致Full GC
老年代空间不足导致Full GC
老年代空间分配担保机制导致Full GC
显式调用 System.gc() 导致Full GC(生产环境一般会通过 -XX:+DisableExplictGC 参数禁用显式GC)