文章

JVM 调优

调优工具

jmap

jmap 是jdk自带的用来查看java程序内存信息的工具

pid 可通过 jps 获取

1⃣️ 查看Java对象实例信息:jmap -histo {pid} > {pid}.histo

  1. num: 序号

  2. instances: 实例数量

  3. bytes: 内存占用大小

  4. 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时自动导出堆快照:

  1. OOM时导出:-XX:+HeapDumpOnOutOfMemoryError

  2. 导出路径:-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}

  1. S0C:Survivor0 大小(KB)

  2. S1C:Survivor1 大小(KB)

  3. S0U:Survivor0 已使用大小(KB)

  4. S1U:Survivor1 已使用大小(KB)

  5. EC:Eden 大小(KB)

  6. EU:Eden 已使用大小(KB)

  7. OC:老年代大小(KB)

  8. OU:老年代已使用大小(KB)

  9. MC:元空间大小(KB)

  10. MU:元空间已使用大小(KB)

  11. CCSC:压缩类空间大小,跟指针压缩有关(KB)

  12. CCSU:压缩类空间已使用大小(KB)

  13. YGC:Young GC 总次数

  14. YGCT:Young GC 总耗时(s)

  15. FGC:Full GC 总次数

  16. FGCT:Full GC 总耗时(s)

  17. GCT:GC 总耗时(s)

jvisualvm

远程连接jvisualvm:(生产环境不允许使用)

  1. 配置服务器ip:`-Djava.rmi.server.hostname={ip}`

  2. 配置jmx端口:`-Dcom.sun.management.jmxremote.port={port}`

  3. 关闭jmx的ssl:`-Dcom.sun.management.jmxremote.ssl=false`

  4. 关闭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 命令)

命令使用举例:

  1. dashboard 查看总览

  1. thread 查看线程栈

  1. jad 反编译Java类

  1. ognl 查看和修改线上实例属性

(5) 退出Arthas

  1. 退出当前连接:`quit` 或 exit

  2. 完全退出:`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的场景:

  1. 元空间大小不足导致Full GC

  2. 老年代空间不足导致Full GC

  3. 老年代空间分配担保机制导致Full GC

  4. 显式调用 System.gc() 导致Full GC(生产环境一般会通过 -XX:+DisableExplictGC 参数禁用显式GC)

License:  CC BY 4.0