XX省一个正式上线运行的系统,每运行一段时间后程序进程会莫名其妙地被kill掉,不得不手工启动系统。
监控结果 jmap命令查看堆内存分配和使用情况/jmap -heap 31 //31为程序的进程号
Attaching to process ID 31, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 11.0-b12 //显示jvm的版本号
using parallel threads in the new generation. //说明在年轻代使用了并行收集
using thread-local object allocation.
Concurrent Mark-Sweep GC //启用CMS收集模式
Heap Configuration:
MinHeapFreeRatio = 40
MaxHeapFreeRatio = 70 //这两项说明堆内存的使用比例在30%~60%之间
MaxHeapSize = 2147483648 (2048.0MB) //最大堆大小为2048M
NewSize = 805306368 (768.0MB)
MaxNewSize = 805306368 (768.0MB) //年轻代大小为768M
OldSize = 1342177280 (1280.0MB) //老年代代大小为1280M
NewRatio = 8 //这个有点自相矛盾,1:8
SurvivorRatio = 3 //救助区大小占整个年轻代的五分之一
PermSize = 268435456 (256.0MB) //持久代大小为256M
MaxPermSize = 268435456 (256.0MB) //持久代大小为256M
Heap Usage:
//年轻代大小,这里只计算了一个救助区,所以少了153M
New Generation (Eden + 1 Survivor Space):
capacity = 644284416 (614.4375MB)
used = 362446760 (345.65616607666016MB)
free = 281837656 (268.78133392333984MB)
56.25570803810968% used
//Eden Space大小为614.43-153=460.8M
Eden Space:
capacity = 483262464 (460.875MB)
used = 342975440 (327.0868682861328MB)
free = 140287024 (133.7881317138672MB)
70.97084204743864% used
//两个救助区的大小均为153MB, 与前面的SurvivorRatio参数设置值计算结果一致。
From Space:
capacity = 161021952 (153.5625MB)
used = 19471320 (18.569297790527344MB)
free = 141550632 (134.99320220947266MB)
12.092338813530219% used
To Space:
capacity = 161021952 (153.5625MB)
used = 0 (0.0MB)
free = 161021952 (153.5625MB)
0.0% used
//老年代大小为1280M,和根据参数配置计算的结果一致。
concurrent mark-sweep generation:
capacity = 1342177280 (1280.0MB)
used = 763110504 (727.7588882446289MB)
free = 579066776 (552.2411117553711MB)
56.85616314411163% used
//永久代大小为256M,实际使用不到50%。可在系统运行一段时间后稳定该值。
Perm Generation:
capacity = 268435456 (256.0MB)
used = 118994736 (113.48222351074219MB)
free = 149440720 (142.5177764892578MB)
44.32899355888367% used
Top命令监控结果:
通过使用top命令进行持续监控发现此时CPU空闲比例为85.7%,剩余物理内存为3619M,虚拟内存8G未使用。持续的监控结果显示进程29003占用系统内存不断在增加,已经快得到最大值。
使用jstat命令对PID为29003的进程进行gc回收情况检查,发现由于Old段的内存使用量已经超过了设定的80%的警戒线,导致系统每隔一两秒就进行一次FGC,FGC的次数明显多余YGC的次数,但是每次FGC后old的内存占用比例却没有明显变化—系统尝试进行FGC也不能有效地回收这部分对象所占内存。同时也说明年轻代的参数配置可能有问题,导致大部分对象都不得不放到老年代来进行FGC操作,这个或许跟系统配置的会话失效时间过长有关。
在上图中发现大量的的工作流线程锁定。
在上图中发现大量的的cms线程池管理线程锁定。
通过对jvm内存进行实时监控后发现导致老年代内存不能有效回收的原因就在于堆栈中存在大量的线程死锁问题。建议开发组认真审查com.zzxy.workflow包的源代码以及com.web.csm包中的源代码,看看是否存在线程死锁的缺陷。
系统的JVM设置-XX:+PrintGCApplicationConcurrentTime -XX:+PrintGCApplicationStoppedTime
-XX:+PrintGCTimeStamps
-XX:+PrintGCDetails
-Xms2048m
-Xmx2048m
-server
-Djava.awt.headless=true
-XX:PermSize=256m
-XX:MaxPermSize=256m
-XX:+DisableExplicitGC
-Xmn768M
-XX:SurvivorRatio=3
-Xss128K
-XX:TargetSurvivorRatio=80
-XX:MaxTenuringThreshold=5
-XX:+UseConcMarkSweepGC
-XX:+CMSClassUnloadingEnabled
-XX:+UseCMSCompactAtFullCollection
-XX:-CMSParallelRemarkEnabled
总结
1、性能调优要做到有的放矢,根据实际业务系统的特点,以一定时间的JVM日志记录为依据,进行有针对性的调整、比较和观察。
2、性能调优是个无止境的过程,要综合权衡调优成本和更换硬件成本的大小,使用最经济的手段达到最好的效果。
3、性能调优不仅仅包括JVM的调优,还有服务器硬件配置、操作系统参数、中间件线程池、数据库连接池、数据库本身参数以及具体的数据库表、索引、分区等的调整和优化。
4、通过特定工具检查代码中存在的性能问题并加以修正是一种比较经济快捷的调优方法。