JVM内存设置-关于缓存

losetowin 发布于:2018-3-4 22:43 分类:Java  有 941 人浏览,获得评论 0 条 标签: java_neicun_shezhi_guanyu_huancun 

本文地址:http://www.dutycode.com/post-158.html
除非注明,文章均为 www.dutycode.com 原创,欢迎转载!转载请注明本文地址,谢谢。

之前写过一篇关于JVM内存参数设置的文章(http://www.dutycode.com/jvm_xmx_xmn_xms_shezhi.html),设置方式本身没有很大问题,但却忽略了一个点,如果站点中使用了缓存的话, 这种设置方式可能会导致堆内存不够用。 一方面是因为缓存基本上不能被回收掉。另一方面,缓存可能并不是立马就能到达容量上限。所以当系统运行比较长一段时间之后再设置内存,相对会得到一个比较合适的大小。 


但系统运行相对较长一段时间在做内存的大小分配,时间周期会比较长,操作起来时间周期太长,不利于线上操作。 


最近我们遇到了一个线上的Case


年前我们做压测的时候,将某站点部署到沙箱机中进行测试,使用jmap 等方式强制GC后,按照之前的经验,设置了4G堆内存,其中,新生代2G,年老代2G,沙箱机压测时YGC和FGC正常,未出现频繁GC的情况,于是,我们将这个内存设置同步到线上了。


但系统运行一段时间之后,发现系统出现了频繁的FGC,jstat查看GC情况如下:

从上图中可以看出, 基本8s就产生了一次FGC,且FGC之后老年代的内存使用率并未降下来,始终维持在95%左右。 


基本上看到FGC频繁的第一反应就是出现了内存泄露,于是按照教科书的教导,我们对线上的进程打了一个内存快照(jmap -dump:live,format=b,file=heap.bin <pid> ) 


PS:正常线上是不可以直接打内存快照的,因为会导致stw。建议先摘掉机器之后,再打内存快照,防止影响线上服务。


原本想用jhat来提供一个线上访问的入口,但发现服务器在执行jhat的时候耗时太久了,于是将内存快照保存到了本地,用本地的MAT来做了一下查看。


从上图中看的比较清晰,主要是因为ehcache.Cache和ConcurrentHashMap占用了内存


查看代码,发现系统使用ehchache作为缓存,我们线上还有16G的站点,查看线上的情况,基本上老年代维持在1.96-2.38G左右。 


所以问题也就基本明了了, 因为老年代的内存为2G,但缓存又占用的比较大,占用到了1.9G以上,所以就会触发FGC,但缓存中的数据又不能被GC清除掉,所以GC之后,老年代的占比依旧没怎么变化,之后就反反复复的触发FGC了。 


注:触发FGC的条件依据jvm配置,比如如果是CMS的回收器,CMSInitiatingOccupancyFraction 的配置决定了何时触发FGC


知道问题了,解决办法相对也就简单了。 调整jvm的参数,加大堆内存可以解决。


第二阶段:

加大堆内存虽然可以解决,但是依旧可能存在问题


    比如,如果缓存的值大小变大,虽然个数不变,那么也会导致整体所占用内存变大。这样原本设置的堆内存大小可能还会再次出现不够的情况


    缓存数据一般会常驻内存中一段时间,GC无法保证回收时会被回收掉,所以可能导致GC的无用功及GC的频率/时长变多。


有种办法是将本地缓存使用分布式缓存代替,但这样虽然能使本地内存变少,但却带来了额外的网络开销。 


另外一种办法是利用堆外内存来解决,将缓存缓存到堆外,不影响到GC,因为堆外内存增长不涉及到堆参数,即使缓存数据增长的时候,也不会影响到正常的业务逻辑。 


目前ehcache提供了堆外缓存的方式, 可以将缓存设置到堆外。 具体使用参考官方文档:http://www.ehcache.org/documentation/3.0/getting-started.html#off-heap


PS:在使用对外内存的时候,不要忘记添加JVM参数:-XX:MaxDirectMemorySize  来限制下堆外内存的大小。同时,这个-XX:+DisableExplicitGC 参数需要注意, 这个参数设定了之后将禁止显式调用GC,但这个可能会影响到堆外内存的回收。



长按下方二维码关注我~

版权所有:《攀爬蜗牛》 => 《JVM内存设置-关于缓存
本文地址:https://www.dutycode.com/post-158.html
除非注明,文章均为 《攀爬蜗牛》 原创,欢迎转载!转载请注明本文地址,谢谢。