JVM-调优工具
Jmap命令
此命令可以用来查看内存信息,实例个数以及占用内存大小
使用场景
- 定位内存泄漏:对比两次 -histo 输出,找出持续增长的对象。
- 分析大对象:从堆转储中查找占用内存最多的对象。
- 排查 OOM:在应用内存溢出前自动或手动导出堆快照
查看对象统计信息:
jmap -histo 13512
按类名排序显示对象数量、内存占用,帮助定位占用最大的对象类型。
查看堆内存信息
jmap -heap 13512 # JDK 8 及以下有效
包括堆配置(年轻代、老年代大小)、各代空间使用率等。
生成堆转储文件(Heap Dump)
jmap -dump:format=b,file=aaa.hprof 13512
将 Java 堆中所有对象信息导出到 .hprof 文件,供 JVisualVM 等工具分析。
也可以设置内存溢出自动导出dump文件(内存很大的时候,可能会导不出来)
- -XX:+HeapDumpOnOutOfMemoryError
- -XX:HeapDumpPath=./ (路径)
Jstack 命令
检测死锁也可以用jvisualvm自动检测死锁
jstack 13512
定位高 CPU 线程
# ① 找出 Java 进程内 CPU 最高的线程 ID(十进制),显示java进程的内存情况,pid是java进程号,比如19663
top -H -p 12345
# ② 将十进制线程 ID 转为十六进制
printf "%x\n" 12346
# ③ 用 jstack 过滤该十六进制的 nid,得到线程堆栈信息中 0x3a2a这个线程所在行的后面30行,从堆栈中可以发现导致cpu飙高的调用方法
jstack 12345 | grep -A 30 "nid=0x3a2a"
Jinfo命令
查看某个 JVM 参数的当前值
# 查看最大堆内存
jinfo -flag MaxHeapSize 12345
# 查看是否开启 GC 日志
jinfo -flag PrintGCDetails 12345
查看jvm的参数是否可动态修改
jinfo -flags 13512 # 输出中带 manageable 标记的标志可动态调整
查看java系统参数
jinfo -sysprops 13512
动态开启 GC 日志(无需重启)
jinfo -flag +PrintGCDetails 12345 # 立即生效,应用开始输出 GC 日志
Jstat命令
jstat命令可以查看堆内存各部分的使用量,以及加载类的数量。命令的格式如下:
jstat [-命令选项] [vmid] [间隔时间(毫秒)] [查询次数]
注意:使用的jdk版本是jdk8
常用选项
- -class:类加载统计:已加载类数、总字节数、卸载类数、耗时
- -compiler:JIT 编译统计:编译任务数、失败数、耗时
- -gc:最常用:各代内存使用量 + GC 次数/时间
- -gccapacity:各代空间的容量和最大值
- -gcutil:推荐:各代使用率百分比 + GC 次数/时间
- -gccause:同 -gcutil,额外输出上次/本次 GC 的原因
- -gcnew:年轻代详细信息
- -gcnewcapacity:年轻代容量详解
- -gcold:老年代详细信息
- -gcoldcapacity:老年代容量详解
- -gcmetacapacity:元空间容量统计
检查老年代增长趋势评估程序内存使用及GC压力整体情况
jstat -gc 13512
堆内存统计 显示各代空间的容量和最大值
jstat -gccapacity 13512
新生代垃圾回收统计显示年轻代详细信息
jstat -gcnew 13512
JVM运行情况预估
用 jstat gc -pid 命令可以计算出如下一些关键数据,有了这些数据就可以采用之前介绍过的优化思路,先给自己的系统设置一些初始性的JVM参数,比如堆内存大小,年轻代大小,Eden和Survivor的比例,老年代的大小,大对象的阈值,大龄对象进入老年代的阈值等。
年轻代对象增长的速率 -
可以执行命令 jstat -gc pid 1000 10 (每隔1秒执行1次命令,共执行10次),通过观察EU(eden区的使用)来估算每秒eden大概新增多少对象,如果系统负载不高,可以把频率1秒换成1分钟,甚至10分钟来观察整体情况。注意,一般系统可能有高峰期和日常期,所以需要在不同的时间分别估算不同情况下对象增长速率。
每秒产生多少对象 约为 eden区每秒添加多少数据,
Young GC的触发频率和每次耗时
知道年轻代对象增长速率我们就能推根据eden区的大小推算出Young GC大概多久触发一次,Young GC的平均耗时可以通过 YGCT/YGC公式算出,根据结果我们大概就能知道系统大概多久会因为Young GC的执行而卡顿多久。
每次Young GC后有多少对象存活和进入老年代
这个因为之前已经大概知道Young GC的频率,假设是每5分钟一次,那么可以执行命令 jstat -gc pid 300000 10 ,观察每次结果eden,survivor和老年代使用的变化情况,在每次gc后eden区使用一般会大幅减少,survivor和老年代都有可能增长,这些增长的对象就是每次Young GC后存活的对象,同时还可以看出每次Young GC后进去老年代大概多少对象,从而可以推算出老年代对象增长速率。
Full GC的触发频率和每次耗时
知道了老年代对象的增长速率就可以推算出Full GC的触发频率了,Full GC的每次耗时可以用公式 FGCT/FGC 计算得出。
优化思路其实简单来说就是尽量让每次Young GC后的存活对象小于Survivor区域的50%,都留存在年轻代里。尽量别让对象进入老年代。尽量减少Full GC的频率,避免频繁Full GC对JVM性能的影响。
阿里巴巴Arthas
命令
dashboard:查看整个进程的运行情况,线程、内存、GC、运行环境信息
thread:可以查看线程详细情况
thread 线程ID :可以查看线程堆栈信息
thread -b :可以查看线程死锁
jad 类的全名 :反编译,这样可以方便我们查看线上代码是否是正确的版本
ognl :直接在程序运行时直接修改内存数据
排查内存问题
当服务器出现应用响应变慢、CPU 飙升、GC 日志显示频繁 Full GC、最终抛出 OutOfMemoryError、发现老年代或元空间持续增长且无法回收时:
第一步:用 jstat -gcutil 或 -gc 快速评估 GC 状况
1:E (Eden):如果长期接近100%,Ygc频繁,说明对象分配过快。
2:O(old): 若持续上升且不下降,存在内存泄漏
3:M (Metaspace):持续上升 → 类加载泄漏(如动态生成类、Groovy、热部署)。
4:YGC / FGC:YGC 每秒超过 5-10 次,或 FGC 频繁(几分钟一次) → 需要深入分析。
第二步:使用jmap查看堆中存活对象的统计(会触发一次 Full GC)
1:若某个自定义对象(如 MyLargeObject)或 byte[] 占用极高,且数量持续增长 ,大概率是泄漏点
2:间隔几分钟对比两次快照,观察哪些类的实例数或字节数增长最快。
第三步:jmap -dump导出堆转储文件,通过VisualVM 可视化工具查看对象分布。
第四步:可以使用jinfo命令临时调整一些诊断参数
第五步:可以使用jstack命令查看线程状态,内存被线程局部变量或 ThreadLocal 长期持有,导致无法回收
1:是否存在大量 WAITING 或 BLOCKED 线程,且它们持有大对象引用。
2:搜索 ThreadLocal 相关的栈帧,看是否有未调用 remove()。
如何定位 YGC 的原因
结合 jstat -gcutil -pid 查看 s1,s2,eden区域使用情况,YGC总次数,YGCT总耗时。结合堆内存查看
如:fullgc能回收大量内存,可能年轻代设置偏小,将短命对象流入了老年代,通过 -Xmx -Xms -Xmn设置年轻代大小,或设置年代s1,s2,eden比例。
Full GC 的常见触发原因
1:晋升失败,Young GC 后存活对象移入老年代,老年代空间不足,此时需要增大堆内存、优化缓存、检查对象引用
2:大对象直接进入老年代
3:老年代碎片化(CMS 容易出现此情况)
4:元空间内存不足,此时需要增大 MaxMetaspaceSize、排查类加载器泄漏
5:老年代空间分配担保机制
6:cms并发收集时失败(Concurrent Mode Failure),调大 CMS 触发阈值、增大老年代或改用 G1
如何定位 Full GC 的原因
1:启用 GC 日志,观察 GC 日志中的关键字
Promotion Failed、Concurrent Mode Failure → 老年代空间问题
Metadata GC Threshold → 元空间不足
System.gc() → 显式调用
Full GC (Heap Inspection) → 由 jmap -histo:live 触发
2:结合 jstat -gcutil -pid 查看 OC老年代大小,OU老年代使用大小,FGC总次数,FGCT总耗时。结合堆内存查看
- 如果老年代的使用率 在 Full GC 后依然很高,说明可能存在内存泄漏;
- 如果老年代空间没满却频繁 Full GC,检查是否调用了 System.gc() 或元空间是否超限。
- 如果老年代的使用率 在 Full GC 后内存占用迅速下降,说明短命对象进入到了老年代。如大对象直接进入老年代,触发动态年龄分配担保机制。
- fullgc次数比YongGc次数多,甚至接近两倍,可能是老年代分配担保机制,YongGc前查看此次新生代对象占用内存大于每次YongGc后进入老年代的平均值,先出发fullGc,然后出发YongGc,移入到老年代后占用空间达到限制又触发fullGc
生产环境死锁处理过程:
1:使用jps获取线程pid,通过jstack pid 查看线程是否存在Found one Java-level deadlock。
2:或者通过可视化工具查看是否存在死锁。jvisualvm,Jconsole,
紧急处理:
1:通过jstack -pid > deadlock.log 保留死锁日志。用于后续分析。
2:无状态服务重启,或将有状态服务切换为备用节点。
3:代码检查,程序bug,数据库层面是否存在死锁.
死锁解决:
1:所有线程按相同顺序获取锁
2:锁超时
3:减少锁力度
死锁预防:
1:高级并发工具,ConcurrentHashMap,BlockingQueue、AtomicReference等工具
2:避免线程交叉,如果线程间有依赖关系,使用CountDownLatch ,CompletableFuture对线程编排。
3:使用静态代码分析工具,分析死锁。
频繁OOM:
1:系统已经宕机,一定要提前配置导出dump文件。结合jvisualvm加载dump文件进行调试,查看最多与业务关联对象,找到相关gcroots,查看线程栈,找到相关代码。
2:系统未宕机,
2.1使用jmap -dump :fromat=b > xxx.dump导出dump文件,此命令会造成stw,但与快速找出频繁OOM原因相比是值得的,通过可视化工具分析dump文件,如jvisualvm。
2.2通过可视化工具查看,Arthas
更多推荐


所有评论(0)