背景
最近在搞一些大数据智能推荐方面的开发工作,为了保证推荐的实时性,没隔10分钟会启动几个worker遍历所有数据 进行检查。
程序在预发环境运行一段时间后,偶尔会出现堆内存使用率超过80%的情况(公司监控系统默认堆内存使用率超过80%后,就会报警)。
重启后 一段时间内存使用情况是正常的,所以初步怀疑有存在内存泄露。
分析问题
1、按照内存泄露流程排查:ps -ef|grep java 找到对应java工程的进程号(也可以用top)
进程号为6206
2、然后执行jmap -dump:live,file=/tmp/dump.dat 6206,dump文件有点大(几百M),下载下来用 eclipse Memory Analyzer 进行分析(参考
http://blog.csdn.net/xb151652000/article/details/8056792)
遗憾的是没有发现异常,难道不是内存泄漏?
3、查看gc情况:jstat -gcutil 6206
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
0.00 8.33 11.44 76.30 98.24 95.28 9493 1170.869 2 1.964 1172.833
0.00 8.33 11.47 76.30 98.24 95.28 9493 1170.869 2 1.964 1172.833
0.00 8.33 11.47 76.30 98.24 95.28 9493 1170.869 2 1.964 1172.833
0.00 8.33 11.47 76.30 98.24 95.28 9493 1170.869 2 1.964 1172.833
0.00 8.33 11.48 76.30 98.24 95.28 9493 1170.869 2 1.964 1172.833
发现 年老代内存使用率占比,超过70%。初步发现原因所在:YGC 年轻代gc太频繁了(9000多次,从程序启动时算起),FGC fullgc 正常总共就2次。
4、再使用命令:jmap -heap 6206看下各个分代内存大小:
我晕,s0 s1都自动分配的1M。
先看下YGC流程:我们知道每次YGC时,存活下来的对象先从eden复制到s0,年龄为1,第二次YGC还存活从s0复制到s1 年龄为2,第三次YGC 从s1复制到s0 年龄变为3,当到达最大年龄是再复制到年老代(最大年龄配置-XX:MaxTenuringThreshold=4)。
这里s0 s1太小了,根本放不下每次eden中存活下来的对象,形同虚设,所以只能直接迁移到年老代,导致年老代很快打满(YGC时,年轻代内存使用率已经100%)。出现堆内存报警,就不足为奇了。之后就会频繁的出现fullgc(年老代被打满),进入恶性循环。
每隔10分钟YGC次数(worker执行时):
解决问题
问题原因:由于我们没有指定堆内存大小,系统默认分配,并根据内存动态调整各个分代内存大小,最终导致s0 s1区分配过小,每次YGC都将存活的对象迁移到年老代。
解决办法,jvm 参数手动调整为:
-Xmx2048m -Xms2048m -Xmn1g -Xss512k -XX:SurvivorRatio=8 -XX:+UseParallelGC
-XX:MaxTenuringThreshold=4 -XX:ParallelGCThreads=43
-Xmx2048m 最大堆内存2G(之前默认是1G)
-Xms2048m 初始堆内存(建议跟-Xmx设置成一样,避免gc后调整)
-Xmn1g 年轻代内存1G
-Xss512k 每个栈大小为512K(设置小点,可以创建更多的线程,设置太小 会栈溢出)
-XX:SurvivorRatio=8 算法(s0+s1)/eden=2:8,即s0=s1=1/10 * 1G=102.4M
-XX:+UseParallelGC 开启并行gc(我们是后端服务,需要大吞吐量)
-XX:MaxTenuringThreshold=4 年轻代对象年龄为4
-XX:ParallelGCThreads gc线程数,此值最好配置与处理器数目相等。
对比
重新启动程序,看下内存分配情况(jmap -heap pid):
eden = 820M
s0=102M
s1=102M
old=1024M
gc效果(jstat - gcutil pid):
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
28.51 0.00 22.69 5.31 98.04 95.71 6 1.687 2 1.353 3.040
28.51 0.00 22.69 5.31 98.04 95.71 6 1.687 2 1.353 3.040
28.51 0.00 22.69 5.31 98.04 95.71 6 1.687 2 1.353 3.040
28.51 0.00 22.84 5.31 98.04 95.71 6 1.687 2 1.353 3.040
28.51 0.00 22.84 5.31 98.04 95.71 6 1.687 2 1.353 3.040
通过长时间观察O(年老代) 内存使用率长时间维持在 6%左右,对象在s0 s1区里来回复杂,基本都已经被gc掉了。至此问题解决。
优化后,每隔10分钟 YGC次数:
小结
jvm 参数优化不是千篇一律,需要根据具体的业务场景具体调整(我这里是每隔10分钟有一个峰值)。深刻理解每个参数具体含义和用法,反复实践才是关键。
开发中也尽量注意代码质量,比如大数据开发中,遍历10万条数据,一定要分配次 一次少量取数据处理,取下一批数的时候让上一批数据成为垃圾。
尽量不要增量的使用全局的MAP List,如果一定要使用,记得清理。
线程池中使用ThreadLocal,也要注意在结束时remove。