一、优化目标
-
通常来说,我们的 JVM 参数配置大多还是会遵循 JVM 官方的建议,例如:
-XX:NewRatio=2,年轻代:老年代=1:2
-XX:SurvivorRatio=8,eden:survivor=8:1
堆内存设置为物理内存的3/4左右
-
JVM 有哪些核心指标?合理范围应该是多少?
jvm.gc.time:每分钟的GC耗时在1s以内,500ms以内尤佳
jvm.gc.meantime:每次YGC耗时在100ms以内,50ms以内尤佳
jvm.fullgc.count:FGC最多几小时1次,1天不到1次尤佳
jvm.fullgc.time:每次FGC耗时在1s以内,500ms以内尤佳
-
JVM 优化步骤
1)CPU指标 查看占用CPU最多的进程 查看占用CPU最多的线程 查看线程堆栈快照信息 分析代码执行热点 查看哪个代码占用CPU执行时间最长 查看每个方法占用CPU时间比例 2)JVM 内存指标 查看当前 JVM 堆内存参数配置是否合理 查看堆中对象的统计信息 查看堆存储快照,分析内存的占用情况 查看堆各区域的内存增长是否正常 查看是哪个区域导致的GC 查看GC后能否正常回收到内存
3)JVM GC指标 查看每分钟GC时间是否正常 查看每分钟YGC次数是否正常 查看FGC次数是否正常 查看单次FGC时间是否正常 查看单次GC各阶段详细耗时,找到耗时严重的阶段 查看对象的动态晋升年龄是否正常
二、调优案例:metaspace导致频繁FGC问题
服务环境:ParNew + CMS + JDK8
问题现象:服务频繁出现FGC
1)首先查看GC日志,发现出现FGC的原因是metaspace空间不够
2)进一步查看日志发现元空间存在内存碎片化现象
为什么内存碎片化的原因:内存存在碎片化现象就是根据 used 和 capacity 的数据得来的,上面说了元空间的分配以 chunk 为单位,即使一个 ClassLoader 只加载1个类,也会独占整个 chunk,所以当出现 used 和 capacity 两者之差较大的时候,说明此时存在内存碎片化的情况。元空间的分配以 chunk 为单位,当一个 ClassLoader 被垃圾回收时,所有属于它的空间(chunk)被释放,此时该 chunk 称为 Free Chunk,而 committed chunk 就是 capacity chunk 和 free chunk 之和。
Metaspace used 35337K, capacity 56242K, committed 56320K, reserved 1099776K 这边简单解释下这几个参数的意义 used :已使用的空间大小 capacity:当前已经分配且未释放的空间容量大小 committed:当前已经分配的空间大小 reserved:预留的空间大小
3)通过 dump 堆存储文件发现存在大量 DelegatingClassLoader
通过进一步分析,发现是由于反射导致创建大量 DelegatingClassLoader。其核心原理如下: 在 JVM 上,最初是通过 JNI 调用来实现方法的反射调用,当 JVM 注意到通过反射经常访问某个方法时,它将生成字节码来执行相同的操作,称为膨胀(inflation)机制。如果使用字节码的方式,则会为该方法生成一个 DelegatingClassLoader,如果存在大量方法经常反射调用,则会导致创建大量 DelegatingClassLoader。 反射调用频次达到多少才会从 JNI 转字节码? 默认是15次,可通过参数 -Dsun.reflect.inflationThreshold 进行控制,在小于该次数时会使用 JNI 的方式对方法进行调用,如果调用次数超过该次数就会使用字节码的方式生成方法调用。 分析结论:反射调用导致创建大量 DelegatingClassLoader,占用了较大的元空间内存,同时存在内存碎片化现象,导致元空间利用率不高,从而较快达到阈值,触发 FGC。