JVM
性能优化步骤:
JVM
参数JVM
参数模板线上频繁 Full GC
的表现:
机器 CPU
负载过高
频繁 Full GC
报警
系统无法处理请求或者处理过慢
频繁 Full GC
常见原因:
对象频繁进入老年代,频繁触发 Full GC
系统承载高并发请求,或处理数据量过大,导致
Young GC
频繁,每次Young GC
过后存活对象太多,内存分配不合理,Survivor
区域过小。
系统一次性加载过多数据进入内存,大对象直接入老年代,频繁触发 Full GC
内存泄漏,对象无法回收,一直占用在老年代里,频繁触发 Full GC
MetaSpace
(永久代)加载类过多,触发 Full GC
代码中使用 System.gc()
,触发 Full GC
针对以上 Full GC
常见的原因,对应的优化方式:
jstat
分析,合理分配内存,调大 Survivor
区域
dump
出内存快照,用 MAT
工具进行分析,代码上排查
dump
出内存快照,用 MAT
工具进行分析,代码上排查
若内存使用不多,还频繁触发 Full GC
,那么优化加载的类
若内存使用不多,还频繁触发 Full GC
,代码上排查,删除 System.gc()
High Allocation Rate
)分配速率(Allocation rate
)表示单位时间内分配的内存量。
通常使用 MB/sec
作为单位。上一次垃圾收集之后, 与下一次 GC
开始之前的年轻代使用量, 两者的差值除以时间, 就是分配速率。分配速率过高就会严重影响程序的性能, 在 JVM
中可能会导致巨大的 GC
开销。
JVM
启动之后 291 ms, 共创建了 33,280 KB 的对象。第一次 Minor GC
(小型GC
) 完成后, 年轻代中还有 5,088 KB 的对象存活。
在启动之后 446 ms, 年轻代的使用量增加到 38,368 KB , 触发第二次 GC
, 完成后年轻代的使用量减少到 5,120 KB。
在启动之后 829 ms, 年轻代的使用量为 71,680 KB, GC
后变为 5,120 KB。
思考一个问题, 分配速率, 到底影响什么?
想一想, new
出来的对象, 在什么地方。
答案就是, Eden
。
假如我们增加 Eden
, 会怎么样。 考虑蓄水池效应。 最终的效果是, 影响 Minor GC
的次数和时间, 进而影响吞吐量。
在某些情况下, 只要增加年轻代的大小, 即可降低分配速率过高所造成的影响。
增加年轻代空间并不会降低分配速率, 但是会减少 GC
的频率。如果每次 GC
后只有少量对象存活, minor GC
的暂停时间就不会明显增加。
Premature Promotion
)提升速率(promotion rate
)用于衡量单位时间内从年轻代提升到老年代的数据量。
一般使用 MB/sec
作为单位, 和分配速率类似。
JVM
会将长时间存活的对象从年轻代提升到老年代。根据分代假设, 可能存在一种情况, 老年代中不仅有存活时间长的对象, 也可能有存活时间短的对象。
这就是过早提升: 对象存活时间还不够长的时候就被提升到了老年代。
major GC
不是为频繁回收而设计的, 但 major GC
现在也要清理这些生命短暂的对象, 就会导致 GC
暂停时间过长。这会严重影响系统的吞吐量。
GC
之前和之后的年轻代使用量以及堆内存使用量。
这样就可以通过差值算出老年代的使用量。
和分配速率一样, 提升速率也会影响 GC
暂停的频率。但分配速率主要影响 minor GC
, 而提升速率则影响 major GC
的频率。
有大量的对象提升, 自然很快将老年代填满。老年代填充的越快, 则 major GC
事件的频率就会越高。
一般来说过早提升的症状表现为以下形式:
full GC
full GC
后老年代的使用率都很低, 在 10-20% 或以下要演示这种情况稍微有点麻烦, 所以我们使用特殊手段, 让对象提升到老年代的年龄比默认情况小很多。 指定 GC
参数 -Xmx24m -XX:NewSize=16m -XX:MaxTenuringThreshold=1
, 运行程序之后, 可以看到下面的 GC
日志:
解决这类问题, 需要让年轻代存放得下暂存的数据, 有两种简单的方法:
增加年轻代的大小, 设置 JVM
启动参数, 类似这样: -Xmx64m -XX:NewSize=32m
, 程序在执行时, Full GC
的次数自然会减少很多, 只会对 minor GC
的持续时间产生影响。
减少每次批处理的数量, 也能得到类似的结果。至于选用哪个方案, 要根据业务需求决定。在某些情况下, 业务逻辑不允许减少批处理的数量, 那就只能增加堆内存, 或者重新指定年轻代的大小。 如果都不可行, 就只能优化数据结构, 减少内存消耗。
但总体目标依然是一致的: 让临时数据能够在年轻代存放得下。