JVM内存的调优主要的目的是减少GC的频率和Full GC的次数.
一. FullGC
FullGC是对整个堆进行垃圾回收, 会引起STW(stop the world),影响整个服务的运行.
一般引起FullGC的原因有以下几种:
1. 调用System.gc()
2. 老年代空间不足
3. CMS GC时出现promotion failed和concurrent mode failure
promotion failed是在进行Minor GC时,Survivor区放不下, 对象只能放入老年代, 而此时老年代也放不下造成的;
concurrent mode failure是在执行CMS GC的过程中同时有对象要放入老年代, 而此时老年代空间不足造成的;
4. 统计得到年轻代minor gc时晋升到老年代的平均大小大于老年代剩余空间;
可见除了人为操作外,要想减少FullGC,只要减少对象进入老年代的机会,尽量在年轻代回收掉垃圾对象.
二. 优化调整
在调整之前,先一起看下堆的内存区域划分
2.1 大对象调整
当JVM创建一个大对象时,会直接在老年代创建.
针对这种情况, 最好的办法是调整代码设计,减少创建大对象.
2.2 年轻代空间调整
Minor GC, 是针对年轻代的垃圾回收, 当Eden区无法容纳新创建的对象时, 就会触发Minor GC.
在Minor GC时,会将Eden和 Survivor from中的幸存对象存放在Survivor to中, 同时这些对象的年龄 1.
当对象的年龄到达15(根据配置-XX:MaxTenuringThreshold=15)的时候, 对象直接移入老年代;
但是, 还有两种情况也会让对象直接进入老年代,这也是需要特别关注的地方.
1. 当MinorGC后剩余的对象占用空间,大于Survivor to空间的50%(根据配-XX:TargetSurvivorRatio=50), 也会将年龄最大的(即使没有对象年龄没达到15)那部分对象移入老年代;
例如: 年轻代空间为1G,其Survivor空间为100M.
年龄为3的对象: 10M
年龄为2的对象: 20M
年龄为1的对象: 10M
Eden区存活对象: 11M
此时进行MinorGC, 所有存活对象共51M,大于Survivor空间的一半,则年龄为3的10M对象会被移入老年代.
2. 当Survivor空间中相同年龄所有对象的大小总合大于Survivor空间的50%(根据配-XX:TargetSurvivorRatio=50), 则年龄大于等于这个年龄的对象也会直接进入老年代;
例如: 年轻代空间为1G,其Survivor空间为100M.
年龄为1的对象: 20M
Eden区存活对象: 51M
此时进行MinorGC, 因为Eden区的数据大小已经大于Survivor空间的50%, 所以Eden区和年龄为1的对象(共71M), 都会被移入年老代.
三. 解决方案
针对这两种情况,需要调大年轻代以及其Survivor区域大小, 以减少对象进入老年代的机会;调小年老代,减少无用空间的浪费;
1. 调小年老代, 调大年轻代, 默认为2:1, 通过参数-Xmn=2G调大年轻代空间, 则Survivor空间为200M, 足够存放51M对象;
2. 调整Eden区和Survivor区比例,默认8:1:1, 通过-XX:SurvivorRatio=3参数, 将Survivor调大;
3. 调整-XX:TargetSurvivorRatio=60, 也足够存放51M对象, 但这种方式会造成浪费老年代的空间(因为大部分空间用不到), 而且效果不算好, 当业务波动时, 还是大概率会出现超限情况;
经过以上一系列调整, 就会达到我们的目的, 减少GC频率和FullGC次数.
注意: 以上参数一般只需要在高并发的情况下才会做细致调整;
另外,还要求调整人员对系统也要有一定的了解, 知道业务在一定时间内会产生多少垃圾对象, 才能对参数做更准确的定位.