一次jvm调优

背景

 

最近在搞一些大数据智能推荐方面的开发工作,为了保证推荐的实时性,没隔10分钟会启动几个worker遍历所有数据 进行检查。

 

程序在预发环境运行一段时间后,偶尔会出现堆内存使用率超过80%的情况(公司监控系统默认堆内存使用率超过80%后,就会报警)。

 

重启后 一段时间内存使用情况是正常的,所以初步怀疑有存在内存泄露。

 

分析问题

 

1、按照内存泄露流程排查:ps -ef|grep java 找到对应java工程的进程号(也可以用top



 

进程号为6206

 

2、然后执行jmap -dump:live,file=/tmp/dump.dat 6206dump文件有点大(几百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。

猜你喜欢

转载自moon-walker.iteye.com/blog/2372708