16.JVM调优工具详解及调优实战

196 阅读7分钟

JVM自带工具

jmap 查看内存信息

#查看历史生成的实例
jmap -histo [进程id]
#查看当前存活的实例
jmap -histo:live [进程id]
#导出文件
jmap ‐dump:format=b,file=[文件名称] [进程id]  

可以用jvisualvm命令工具导入该dump文件分析

jstack

#查看堆信息,可以检查死锁
jmap [进程id] 

通过jstack找出占用cpu最高的线程堆栈信息

#jps 查找出进程id
top -p [进程id]
# 按H,获取每个线程的内存情况,找到线程id
# 把线程id的十六进制表示
# 得到线程堆栈信息中 4cd0 这个线程所在行的后面10行,从堆栈中可以发现导致cpu飙高的调用方法
jstack [进程id]|grep -A 10 4cd0
#查看对应的堆栈信息找出可能存在问题的代码

jinfo

#查看正在运行的Java应用程序的扩展参数
jinfo -flags 12128  
#查看java系统参数
jinfo -sysprops 248821

jstat

#①jstat -gc pid 可以评估程序内存使用及GC压力整体情况 各个参数含义如下:
#- 13456 - 指定 Java 进程 ID 为 13456 的进程
#- 2000 - 每隔 2000毫秒采样一次
#- 10000 - 采样 10000 次
jstat -gc 13456 2000 10000 
#堆内存统计
jstat -gccapacity 248821
#新生代垃圾回收统计
jstat -gcnew 248821
#新生代内存统计
jstat -gcnewcapacity 248821
#老年代垃圾回收统计
jstat -gcold 248821
#老年代内存统计
jstat -gcoldcapacity 248821
#元数据空间统计
jstat -gcmetacapacity 248821
#用于显示 Java 堆内存各个区域的使用情况百分比
jstat -gcutil 248821

①垃圾回收统计

S0CS1CS0US1UECEUOCOUMCMUCCSCCCSUYGCYGCTFGCFGCTGCT
幸存区1的大小幸存区2的大小幸存区1使用幸存区2使用伊甸园区伊甸园区使用老年代老年代使用方法区大小(元空间)方法区使用压缩类空间大小压缩类空间使用年轻代垃圾回收次数年轻代垃圾回收消耗时间老年代垃圾回收次数老年代垃圾回收消耗时间垃圾回收消耗总时间

②堆内存统计

NGCMNNGCMXNGCS0CS1CECOGCMNOGCMXOGCOCMCMNMCMXMCCCSMNCCSMXCCSCYGCFGCCGC
新生代最小容量新生代最大容量当前新生代容量幸存区1的大小幸存区2的大小伊甸园区老年代最小容量老年代最大容量当前老年代大小当前老年代大小最小元数据容量最大元数据容量当前元数据空间大小最小压缩类空间大小最大压缩类空间大小当前压缩类空间大小年轻代gc次数老年代GC次数

③新生代垃圾回收统计

S0CS1CS0US1UTTMTTDSSECEUYGCYGCT
对象在新生代存活的次数对象在新生代存活的最大次数期望的幸存区大小年轻代垃圾回收次数年轻代垃圾回收消耗时间

④新生代内存统计

NGCMNNGCMXNGCS0CMXS0CS1CMXS1CECMXECYGCFGC
新生代最小容量新生代最大容量当前新生代容量最大幸存1区大小最大幸存2区大小当前幸存2区大小最大伊甸园区大小当前伊甸园区大小老年代回收次数

⑤老年代垃圾回收统计

MCMUCCSCCCSUOCOUYGCFGCFGCTCGCCGCTGCT
方法区大小方法区使用大小压缩类空间大小压缩类空间使用大小老年代大小老年代使用大小年轻代垃圾回收次数老年代垃圾回收次数老年代垃圾回收消耗时间垃圾回收消耗总时间

⑥老年代内存统计

OGCMNOGCMXOGCOCYGCFGCFGCTCGCCGCTGCT
老年代最小容量老年代最大容量当前老年代大小老年代大小年轻代垃圾回收次数老年代垃圾回收次数老年代垃圾回收消耗时间垃圾回收消耗总时间

⑦元数据空间统计

MCMNMCMXMCCCSMNCCSMXCCSCYGCFGCFGCTCGCCGCTGCT
最小元数据容量最大元数据容量当前元数据空间大小最小压缩类空间大小最大压缩类空间大小当前压缩类空间大小年轻代垃圾回收次数老年代垃圾回收次数老年代垃圾回收消耗时间当前压缩类空间的容量当前压缩类空间总容量垃圾回收消耗总时间

⑧堆内存各个区域的使用情况百分比

S0S1EOMCCSYGCFGCFGCTGCT
幸存1区当前使用比例幸存2区当前使用比例伊甸园区使用比例老年代使用比例元数据区使用比例压缩使用比例年轻代垃圾回收次数老年代垃圾回收次数老年代垃圾回收消耗时间垃圾回收消耗总时间

调优分析

JVM运行情况预估

先给自己的系统设置一些初始性的JVM参数,比如堆内存大小,年轻代大小,Eden和Survivor的比例,老年代的大小,大对象的阈值,大龄对象进入老年代的阈值等

  • 年轻代对象增长的速率
    • 通过 jstat -gc pid 1000 10 观察EU估算每秒新增多少对象,如果系统负载不高,可以更换时间频率
  • Young GC的触发频率和每次耗时
    • 根据EU大小和新增速率 推算出YGC大概多久触发一次,YGC的平均耗时 = YGCT/YGC,系统大概多久会因为YGC的执行而卡顿多久。
  • 每次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性能的影响。

full gc比minor gc还多的原因有哪些

  • 元空间不够导致的多余full gc
  • 显示调用System.gc()造成多余的full gc,这种一般线上尽量通过-XX:+DisableExplicitGC参数禁用,如果加上了这个JVM启动参数,那么代码中调用System.gc()没有任何效果
  • 老年代空间分配担保机制

内存泄露

JVM级缓存就简单使用一个hashmap,于是不断往里面放缓存数据,但是很少考虑这个map的容量问题,结果这个缓存map越来越大,一直占用着老年代的很多空间,时间长了就会导致full gc非常频繁,这就是一种内存泄漏,对于一些老旧数据没有及时清理导致一直占用着宝贵的内存资源,时间长了除了导致full gc,还有可能导致OOM。

这种情况完全可以考虑采用一些成熟的JVM级缓存框架来解决,比如ehcache等自带一些LRU数据淘汰算法的框架来作为JVM级的缓存。