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 字段说明 #
案例分析 #
执行命令:
jstat -gc <pid> 1000 10
(其中 <pid> 替换为Java进程id)
比如:jstat -gc 26132 1000 10 (意思是:对 pid 为 26132 的Java进程执行 jstat 命令,option为 -gc,1000表示每秒钟输出一次监控数据,10表示一共输出10次)
执行结果如下:
执行结果字段名和数据未对齐,所以图中简单通过箭头进行了对齐
这里通过FGC
和FGCT
这两列来进行说明:
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
YGC
和YGCT
以及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
执行结果:
表示已经产生了dump文件 /tmp/heapdump.hprof
将此文件下载到本地电脑中,然后使用MAT软件进行分析,找出堆内存泄漏
的具体地方,如对MAT软件使用不熟,可以参考
MAT软件使用入门
说明:
1、一般出现GC问题,都建议生成heapdump,下载下来通过MAT来进行分析,可以对自己的应用的运行情况更加了解,分析后再做出一些修改JVM启动参数的决定,往往更加准确
2、GC优化需要结合业务分析,了解业务特点再做针对性优化
3、堆空间调大或者调小,均会带来优点和缺点,根据业务性能关注点决定
a.更大的年轻代会使老年代变小,大的年轻代会延长YGC的周期,但会增加每次YGC的耗时;小的老年代会增加FGC的频率
b.更小的年轻代会使老年代变大,小的年轻代会导致YGC很频繁,但每次YGC耗时会减少;大的老年代会减少FGC的频率