Gc分析优化

GC分析优化 #

jstat工具使用 #

jstat(JVM Statistics Monitoring Tool):用于监视虚拟机各种运行状态信息的命令行工具。它可以显示本地或者远程虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据

在没有GUI图形界面,只提供了纯文本控制台环境的服务器上,它将是运行期定位虚拟机性能问题的首选工具。常用于检测垃圾回收问题以及内存泄漏问题

官方文档:[https://docs.oracle.com/javase/8/docs/technotes/tools/unix/jstat.html]

option参数 #

  • 类装载相关的:
    • -class: 显示ClassLoader的相关信息:类的装载、卸载数量、总空间、类装载所消耗的时间等
  • 垃圾回收相关的:
    • -gc:显示与GC相关的堆信息。包括Eden区、两个Survivor区、老年代、永久代等的容量、已用空间、GC时间合计等信息
    • -gccapacity:显示内容与-gc基本相同,但输出主要关注Java堆各个区域使用到的最大、最小空间
    • -gcutil:显示内容与-gc基本相同,但输出主要关注已使用空间占总空间的百分比
    • -gccause:与-gcutil功能一样,但是会额外输出导致最后一次或当前正在发生的GC产生的原因
    • -gcnew:显示新生代GC状况
    • -gcnewcapacity:显示内容与-gcnew基本相同,输出主要关注使用到的最大、最小空间
    • -gcold:显示老年代GC状况
    • -gcoldcapacity:显示内容与-gcold基本相同,输出主要关注使用到的最大、最小空间
    • -gcmetacapacity:显示元空间使用到的最大、最小空间
  • JIT相关的:
    • -compiler:显示JIT编译器编译过的方法、耗时等信息
    • -printcompilation:输出已经被JIT编译的方法

常用option: -gc 字段说明 #

gc参数说明

案例分析 #

执行命令:

jstat -gc <pid> 1000 10

其中 <pid> 替换为Java进程id

比如jstat -gc 26132 1000 10 意思是对 pid 为 26132 的Java进程执行 jstat 命令option为 -gc1000表示每秒钟输出一次监控数据10表示一共输出10次

执行结果如下:

jstat-gc执行结果

执行结果字段名和数据未对齐,所以图中简单通过箭头进行了对齐

这里通过FGCFGCT这两列来进行说明:

FGC FGCT
7520 1218.719
7525 1219.545
7530 1220.370
7536 1221.354
7541 1222.174
7546 1222.994
7551 1223.834
7556 1224.655
7561 1225.639
7567 1226.459

从这些数据,我们可以读取到如下信息:

第一秒FGC次数 = 7525 - 7520 = 5
第二秒FGC次数 = 7530 - 7525 = 5
...
第九秒FGC次数 = 7567 - 7561 = 6

第一秒FGCT = 1219.545s - 1218.719s = 0.826s 平均每次 0.826s / 5 = 0.1652s 
第二秒FGCT = 1220.370s - 1219.545s = 0.825s 平均每次 0.825s / 5 = 0.165s
...
第九秒FGCT = 1226.459s - 1225.639s = 0.82s 平均每次 0.82s / 6 = 0.1367s

YGCYGCT以及GCT的计算方式是同理的

到此你可能心里会有疑问:那么,这些数据到底怎么样才算是有问题的呢?

关于GC频率和GC耗时在什么范围内比较合理这个问题,没有统一答案,它们的值和服务器硬件资源(例如内存大小、CPU处理能力)和软件相关,每个业务不尽相同。

通常比较理想的参考建议:

  • YGC平均耗时 < 50ms
  • YGC执行频率 < 10秒1次
  • FGC平均耗时 < 1s
  • FGC执行频率 < 10分钟1次

这是相对比较理想的情况,我们的应用大多都达不到这样的状态,但也不能说明我们的应用就是有严重GC问题,只能说明还有优化空间

仔细观察的同学应该发现了,上述案例中,FGC的执行频率达到了夸张的 6次/s,其实是存在严重问题的

在明确FGC频率有问题后,我们再顺带观察一下OU(老年代使用量(KB))在FGC后的变化情况

OC OU FGC
96256.0 76970.7 7520
96256.0 77015.0 7525
96256.0 78959.7 7530
96256.0 76897.1 7536
96256.0 78860.2 7541
96256.0 78858.3 7546
96256.0 78858.4 7551
96256.0 78858.2 7556
96256.0 76768.1 7561
96256.0 78864.2 7567

可以观察到OU并没有随着FGC有明显的下降(但也没有明显的上升趋势

这种情况下,可以尝试增加堆空间大小来进行解决,可以通过以下参数进行设置:

-Xmx: 设置JVM最大可用堆内存大小,如: -Xmx2048m -Xms: 设置初始堆大小,一般建议和Xmx保持一致,如: -Xms2048m

也有的地方会建议在保持堆空间大小不变的情况下,通过指定年轻代的大小(-Xmn)或者设置年轻代(包括Eden和两个Survivor区)与老年代的比值(不包括永久代)(-XX:NewRatio)来提高 老年代的大小(OC),这里不推荐这种做法,我们现在一般使用G1垃圾回收器,使用G1,一般会配置 -XX:MaxGCPauseMillis 来约束GC操作的最大暂停时间,虽然G1并不能精确保证达到这个目标,但会尽力达到这个目标。如果显式配置了年轻一代的大小,则将无法实现暂停时间目标

以上的场景是: OU并没有随着FGC有明显的下降,但也没有明显的上升趋势,而假如OU随着FGC非但没有下降,还有明显的上升趋势,那应该怎么处理呢?这样的情况,一般怀疑该Java进程存在堆内存泄漏的情况,如果任其发展不进行处理,则有很大可能发生内存溢出,即JVM会抛出: java.lang.OutOfMemoryError: Java heap space

对于堆内存泄漏这种场景,我们一般还需要对堆内存中的对象进行分析,首先我们要获取一个堆内存的dump文件,在这之前,为了排查过程不影响生产(获取dump文件需要执行jmap命令,该命令会使得JVM发生stw(Stop The World)),需要把该问题进程的流量进行关闭(服务实例的流量权重置为0)

确认对生产没有影响后,执行如下命令:

jmap -dump:format=b,file=<filename.hprof> <pid>

比如

jmap -dump:format=b,file=/tmp/heapdump.hprof 26132

执行结果:

heapdump执行结果

表示已经产生了dump文件 /tmp/heapdump.hprof

将此文件下载到本地电脑中,然后使用MAT软件进行分析,找出堆内存泄漏的具体地方,如对MAT软件使用不熟,可以参考 MAT软件使用入门

说明
1一般出现GC问题都建议生成heapdump下载下来通过MAT来进行分析可以对自己的应用的运行情况更加了解分析后再做出一些修改JVM启动参数的决定往往更加准确
2GC优化需要结合业务分析了解业务特点再做针对性优化
3堆空间调大或者调小均会带来优点和缺点根据业务性能关注点决定
	a.更大的年轻代会使老年代变小大的年轻代会延长YGC的周期但会增加每次YGC的耗时小的老年代会增加FGC的频率
	b.更小的年轻代会使老年代变大小的年轻代会导致YGC很频繁但每次YGC耗时会减少大的老年代会减少FGC的频率