Java GC记录
近来、项目没有特别忙碌的时候,抽空看了下生产环境的项目运行状况,我们的项目一直运行速度不是很快,偶尔会出现卡顿的现象,这点给人的体验感觉也就不那么好了。先抛个测试环境截图(生产环境不方便,单参数设置类似):
由上图可以看出,系统分配的堆内存4G,非堆内存最大2G,理论上、这个数值对于一般的项目绝对是够用的了;然鹅、并非如此,事实情况确是:
对于系统GC的记录统计为:
到这里、似乎还是看不出为何会产生这么多次的Full GC(大神应该已经察觉出上面的问题),再来看一张图:
似乎问题在这里已经浮现了,虽然我们给Java的堆内存、非堆内存都分配了不少空间,但真正使用的到的空间,以及它们之间如何分配,却没有限定。如上图存在的问题:
- 整个堆内存年轻代与老年代分别各占1.25G和2.67G,比例大约1:2.
- 永生带使用内存峰值也就只有380M
- 整个堆内存与非堆内存总共分配4.52G,实际使用峰值时也只有3.87G
现在、再分析下造成频繁Full GC的原因,了解过JVM GC知识的基本都清楚当在年轻代new一个对象时,如果年轻代没有足够的内存会进行一次Minor GC,当Minor GC之后仍无法提供足够的内存时,年轻代会将数据移向老年代,若老年代内存充足,则直接移向老年代,若老年代内存不足,就会进行一次Full GC,当Full GC结束后,老年代依旧无法提供足够的内存空间时,将抛出OutOfMemeryError错误;这里显然不是抛出了异常,而是频繁的进行Full GC。还有一种情况是非堆区内存不足时也会触发Full GC,然而,上面的数据显示非堆区内存充足,这里显然不是频繁Full GC的原因。所以、可以肯定这里的Full GC大多是老年代内存不足造成的。
看到上面的堆内存分布,我们知道老年代的内存占用峰值2.67G,依旧造成了频繁的Full GC,分析具体原因如下:
- 年轻代内存占比偏大,导致每次转移的Survivor区数据到老年代过大,致使老年代触发Full GC,即使老年代可能还没满。
- 系统new的大对象较多,在老年代对象存活时间久,致使剩余的内存空间不足
- 由于业务需求的原因,事实就是堆内存分配不足
看到上面的原因分析,解决办法如下:
- 修改年轻代与老年代堆内存使用比例(如1:3,1:4),使得Minor GC次数增多,从而减少Full GC次数。
- 优化业务代码,提高对象的复用,使得大对象的创建数目减少。
- 增加堆内存的分配
总之、具体问题具体分析,希望可以解决问题。