经过前几篇的铺垫,现在开始正式进入调优篇,也是大火实际用的到的和感兴趣的,但是前期的知识积累还是有必要的,所以还对JVM基础没什么了解的,建议还是回看主包的前几篇内容,当然看其他优秀的博主也是可以的。
性能调优三步骤
- 发现问题——性能监控:我们通过GUI图形工具或者JVM命令后对程序进行监控,如出现FullGC频率过高、CPU负载较高、死锁、OOM内存泄漏/溢出,或者更加简单的就是程序莫名其妙响应时间过长大白话就是卡顿。
- 排查问题——性能分析:通过对内存的分析、查看GC日志、查看JVM状态、已经堆栈信息来确定大概的调优方向,分析出那个区域需要调整。
- 解决问题——性能调优:经过监控和分析已经知道了问题的大致方向,然后进行处理,比如增加内存的大小、使用消息队列缓存、增加机器也就是分布式减轻压力、优化代码控制内存的使用。
这个图来自于尚硅谷的宋红康老师的课程,主包也是跟着宋老师学习整理的资料,想学习的更加细节和系统化可以去某站搜索。
jps:列出当前用户的所有Java进程
jps -lvm
//-q:只显示进程ID
//-l:显示完整包名
//-v:显示JVM参数
//-m:显示main方法的参数jps -l
22417 jdk.jcmd/sun.tools.jps.Jps //jps也是个java进程
22260 org.jetbrains.jps.cmdline.Launcher //idea编译进程
2422 //僵尸或者特殊进程不用理会
32988 com.example.seed.SeedApplication//项目进程
89423 com.intellij.idea.Main //idea主进程
这里说一下,其实我们使用的命令全部是在JDK的bin目录下,主包这个是Mac文件的类型显示的就是Unix可执行的文件,要是Window就应该是.exe的文件,有兴趣的小伙伴可以自己去查看一下。 另外这个脚步使用的文件就在/home/lib/tools.jar中,在jar包的/sun/tools目录下,也是一个字节码文件,又兴趣的小伙伴可以自己去查看,这边就不演示了。(如果使用了-XX:-UserPerfData那么jps和jstat都无法探查这个进程,因为关闭了用户性能数据)
jstat
- JVM统计监控工具
jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]
--<option>//操作参数(必填)
--[-t] //JVM启动到现在的时间
[-h<lines>]//每几行重新打印表头
--<vmid> //Java进程Pid
[<interval>//每次打印的间隔时间1000=1秒
[<count>]]//打印次数
选项 | 说明 |
---|---|
-class | 类加载统计 |
-compiler | JIT编译器统计 |
-gc | GC统计 |
-gccapacity | 各内存区域容量统计 |
-gcutil | GC统计百分比 |
-gccause | GC统计及原因(同-gcutil ,但包含最近一次GC原因) |
-gcnew | 新生代GC统计 |
-gcnewcapacity | 新生代内存容量统计 |
-gcold | 老年代GC统计 |
-gcoldcapacity | 老年代内存容量统计 |
-gcmetacapacity | 元空间内存容量统计 |
-printcompilation | JVM编译方法统计 |
图太小的话就放大来看吧,这里就是完整的命令格式,当然必须要大就是操作和PID参数其他的看各自的需求了,这个命令也是查看JVM最多的命令,可以查的消息还是很全面的。
列名 | 说明 | 示例值 |
---|---|---|
Timestamp | JVM 启动后的时间戳(秒) | 134663.6 |
S0C | Survivor 0 区容量(KB) | 0.0 |
S1C | Survivor 1 区容量(KB) | 6144.0 |
S0U | Survivor 0 区已使用(KB) | 0.0 |
S1U | Survivor 1 区已使用(KB) | 6144.0 |
EC | Eden 区容量(KB) | 51200.0 |
EU | Eden 区已使用(KB) | 32768.0 |
OC | 老年代容量(KB) | 40960.0 |
OU | 老年代已使用(KB) | 32549.8 |
MC | Metaspace 容量(KB) | 54528.0 |
MU | Metaspace 已使用(KB) | 53845.6 |
CCSC | 压缩类空间容量(KB) | 7488.0 |
CCSU | 压缩类空间已使用(KB) | 7206.5 |
YGC | 年轻代 GC 次数 | 10 |
YGCT | 年轻代 GC 总时间(秒) | 0.126 |
FGC | Full GC 次数 | 0 |
FGCT | Full GC 总时间(秒) | 0.000 |
CGC | Concurrent GC 次数 | 4 |
CGCT | Concurrent GC 总时间(秒) | 0.002 |
GCT | 所有 GC 总时间(秒) | 0.128 |
这个呢就是各个打印代表的意思了。其他的操作主包我就不演示了。其实使用这个命令就可以判断内存是否有益处的风险,比如我们采用抽样调查的方法,相同的时间间隔下去对老年区进行监控,如果老年区内存占用在某一个时间段激增或者每段时间都增加的比较多,可能就是内存泄漏无法回收了。因为一般情况下除了静态文件和Spring容器、数据库连接池、监听器等不会被回收,其他的基本上这个请求已结束等到下一个E去GC就会被回收的,如果一直没有被回收肯定就是内存泄漏了。
jinfo
:查看和修改正在运行的 Java 进程的配置参数
jinfo [option] <pid>
参数选项 | 作用 |
---|---|
-flags | 显示所有 JVM 参数(包括默认参数) |
-sysprops | 显示所有系统属性(相当于 System.getProperties()) |
-flag <name> | 显示指定 JVM 参数的值 |
-flag [+/-]<name> | 启用或禁用指定的布尔型 JVM 参数 |
-flag <name>=<value> | 设置指定的 JVM 参数值 |
无参数 | 同时显示系统属性和 JVM 参数 |
//查看最大的堆空间值jinfo -flag MaxHeapSize 32988
-XX:MaxHeapSize=4294967296-flag [+/-]<name> - 修改布尔型参数
# 启用PrintGCDetails
jinfo -flag +PrintGCDetails 12345-flag <name>=<value> - 修改数值型参数
# 修改MaxHeapFreeRatio
jinfo -flag MaxHeapFreeRatio=70 12345
好了主包这里只示范简单的几个命令,主包个人其实这个命令只有两个操作,一个是flags一个就是flag,也很好记忆了,而且用处也是非常大的,就比如这个动态修改JVM参数了。不加任何参数查看的信息就非常的全了,大火快去自行尝试一下。另外不是所有的JVM参数都能被修改的,这也是人之常情了,只有被manageable标记的参数。java -XX:+PrintFlagsFinal | grep manageable这个命令可以查看,大概能改的参数就是这些了,主包这个是JDK21可能和大家的不一样,大家还是自己看看吧。
jmap
:用于生成Java堆转储快照(heap dump)和分析堆内存使用情况
这个命令的作用主要就是生成dump的二进制文件,然后进行内存分析的,当然需要借助工作不然我们可是看不懂二进制文件的,然后就是查询类加载器信息、查看finalizer队列、分析对象内存分布(用的多多主包已经飙红了)。
jmap [options] <pid> # 连接运行中的Java进程
jmap [options] <executable> <core> # 连接核心转储文件
jmap [options] [server_id@]<remote server IP or hostname> # 连接远程调试服务器
参数 | 功能描述 | 适用场景 |
---|---|---|
-dump | 生成堆转储文件(Heap Dump) | 内存泄漏分析、OOM 问题诊断 |
-heap | 显示堆内存配置和使用摘要 | 快速查看堆内存分配情况 |
-histo | 显示堆中对象统计直方图 | 分析对象内存占用 |
-clstats | 显示类加载器统计信息 | 类加载问题诊断 |
-finalizerinfo | 显示等待 finalize 的对象 | 对象回收问题诊断 |
-F | 强制模式(当进程挂起时使用) | JVM 无响应时的强制转储 |
#活着的对象
jmap -dump:[live,]format=b,file=<filename> <pid>
#全部的对象包括死的
jmap -dump:format=b,file=<filename> <pid>
//例如
jmap -dump:live,format=b,file=./Downloads/testH.hprof 32988
关键词 | 作用 | 是否可选 | 示例值 |
---|---|---|---|
-dump | 主命令,表示要执行堆转储操作 | 必选 | - |
live | 只转储存活对象(会触发Full GC) | 可选 | 包含或不包含 |
format=b | 指定输出格式为二进制 | 必选 | 必须为b |
file=<filename> | 指定输出文件路径 | 必选 | heap.hprof |
<pid> | 目标Java进程ID | 必选 | 1234 |
在我们分析的时候大多数选择的都是存活的对象,因为 一般没有被回收的对象就是存活的而且要死全部都看的话一个是这个文件回很大,另一个就是分析起来不是那么好分析需要的时间也就是更长了,此外还有一个办法可以获得dump文件,那就是JVM参数,因为大多少程序出现OOM崩溃的时候开发人员是不能第一时间获得这个文件的,来不及和不可预测所有我们需要使用JVM参数来获取崩溃时的内存快照。
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/path/to/dump/file.hprof
这俩参数相信大家也能看明白我就不介绍了。下面就是-heap和-histo的运行实例了,其他的就不演示了。
#查看堆内存信息
jmap -heap <pid>
Attaching to process ID 12345, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.131-b11using thread-local object allocation.
Parallel GC with 4 thread(s) # 使用的GC类型Heap Configuration: # 堆配置参数MinHeapFreeRatio = 40MaxHeapFreeRatio = 70MaxHeapSize = 2147483648 (2048.0MB) # 最大堆NewSize = 715653120 (682.5MB) # 新生代大小MaxNewSize = 715653120 (682.5MB)OldSize = 1431830528 (1365.5MB) # 老年代大小NewRatio = 2 # 新生代/老年代比例SurvivorRatio = 8 # Eden/Survivor比例MetaspaceSize = 21807104 (20.796875MB) # 元空间CompressedClassSpaceSize = 1073741824 (1024.0MB)G1HeapRegionSize = 0 (0.0MB)Heap Usage: # 堆使用情况
PS Young Generation # 并行年轻代
Eden Space: # Eden区capacity = 537919488 (513.0MB)used = 536870912 (512.0MB)free = 1048576 (1.0MB)99.8% used
From Space: # Survivor From区capacity = 89128960 (85.0MB)used = 0 (0.0MB)free = 89128960 (85.0MB)0.0% used
To Space: # Survivor To区capacity = 89128960 (85.0MB)used = 0 (0.0MB)free = 89128960 (85.0MB)0.0% used
PS Old Generation # 并行老年代capacity = 1431830528 (1365.5MB)used = 536870912 (512.0MB)free = 894959616 (853.5MB)37.5% used# 其他信息...
jmap -histo[:live] <pid> [> 输出文件]
# 统计所有对象(包含未被回收的对象)前20个
jmap -histo 12345 | head -20jmap -histo:live 32988 | head -20num #instances实例数量 #bytes字节大小 class name (module)
-------------------------------------------------------1: 81074 12879208 [B (java.base@21.0.5)2: 73927 1774248 java.lang.String (java.base@21.0.5)3: 18267 1607496 java.lang.reflect.Method (java.base@21.0.5)4: 48478 1551296 java.util.concurrent.ConcurrentHashMap$Node (java.base@21.0.5)5: 13055 1546952 java.lang.Class (java.base@21.0.5)6: 7083 1236824 [I (java.base@21.0.5)7: 13298 842456 [Ljava.lang.Object; (java.base@21.0.5)8: 6582 485232 [Ljava.util.HashMap$Node; (java.base@21.0.5)9: 12046 481840 java.util.LinkedHashMap$Entry (java.base@21.0.5)10: 7367 471488 java.util.LinkedHashMap (java.base@21.0.5)11: 393 452112 [Ljava.util.concurrent.ConcurrentHashMap$Node; (java.base@21.0.5)12: 11288 361216 java.util.HashMap$Node (java.base@21.0.5)13: 18351 293616 java.lang.Object (java.base@21.0.5)14: 10053 260904 [Ljava.lang.Class; (java.base@21.0.5)15: 4872 233856 java.lang.invoke.MemberName (java.base@21.0.5)16: 8861 212664 org.springframework.core.MethodClassKey17: 2970 166320 org.springframework.core.ResolvableType18: 353 158216 [Ljdk.internal.vm.FillerElement; (java.base@21.0.5)
总结
本篇主要讲了进行性能调优要做的那些步骤,以及jps、jstat、jinfo、jamp的命令介绍,请大家一定要多多尝试,光看肯定是记不住的。