45、JVM实战:GC

28–GC


1、GC性能方面的考虑

主要有2个方面的指标:
1、 吞吐量:gc的时间占总的时间比,工作时间不算;
2、 暂停时间:gc发生时app对外显示的无法响应;

1.1、堆空间

1、 默认情况下,vm会增加/减少堆空间大小以维持freespace在整个vm中占的比例;
2、 这个比例由MinHeapFreeRatio和MaxHeapFreeRatio指定;

一般而言,server端的app会有以下规则
1. 对vm分配尽可能多的memory;
2. 将Xms和Xmx设为一样的值。
	1. 如果虚拟机启动时设置使用的内存比较小,这个时候又需要初始化很多对象,虚拟机就必须重复地增加内存。
3. 处理器核数增加,内存也跟着增大。

1.2、年轻代

1、 另外一个对于app流畅性运行影响的因素是年轻代的大小;

1、 年轻代越大,minorgc次数越少;
2、 在固定堆空间情况下,更大的年轻代;

1.  意味更小的老年代
2.  意味着 老年代垃圾收集 次数变多
3.  意味着 Full GC 次数变多

2、 NewRatio表示年轻代和老年代的大小比例NewSize和MaxNewSize反映的是年轻代大小的下限和上限,将这两个值设为一样就固定了年轻代的大小(同Xms和Xmx设为一样);
3、 SurvivorRatio是eden和survior大小比例,可以优化survivor的大小,不过这对于性能的影响不是很大;

一般而言,server端的app会有以下规则
1. 首先决定能分配给vm的最大的堆空间,然后设定最佳的年轻代的大小;
2. 如果堆空间固定后,增加年轻代的大小意味着减小老年代大小。
	1. 让老年代在任何时候够大,能够容纳所有live的对象(留10%-20%的空余)。

2、GC 经验

2.1、年轻代大小选择

2.1.1、响应时间优先的应用

1、 尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择);
2、 这种情况下,年轻代收集发生的频率也是最小的,同时也会减少到达老年代的对象;

2.1.2、吞吐量优先的应用

1、 尽可能的设置大;
2、 大小可能到达Gbit的程度,因为对响应时间没有要求,垃圾收集可以并行进行;
3、 一般适合8CPU以上的应用;

2.1.3、避免设置过小,过小会导致

1、 年轻代GC次数更加频繁;
2、 可能导致年轻代对象直接进入老年代,如果此时老年代满了,会触发FGC;

2.2、老年代大小选择

2.2.1、响应时间优先的应用

老年代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数。

1、 如果堆设置小了,可以会造成内存碎片,高回收频率以及应用暂停而使用传统的标记清除方式;
2、 如果堆大了,则需要较长的收集时间;
3、 最优化的方案,一般需要参考以下数据获得:;

1、 并发垃圾收集信息;
2、 持久代并发收集次数;
3、 传统GC信息;
4、 花在年轻代和老年代回收上的时间比例;

2.2.2、吞吐量优先的应用

一般吞吐量优先的应用都有一个很大的年轻代和一个较小的老年代,这样可以尽可能回收掉大部分短期对象,减少中期的对象,而老年代尽存放长期存活对象。

2.3、较小堆引起的碎片问题

因为老年代的并发收集器使用标记,清除算法,所以不会对堆进行压缩,当收集器回收时,他会把相邻的空间进行合并,这样可以分配给较大的对象。但是,当堆空间较小时,运行一段时间以后,就会出现"碎片",如果并发收集器找不到足够的空间,那么并发收集器将会停止,然后使用传统的标记,清除方式进行回收。

如果出现"碎片",可能需要进行如下配置:
1. -XX:+UseCMSCompactAtFullCollection:使用并发收集器时,开启对老年代的压缩。
2. -XX:CMSFullGCsBeforeCompaction=0:上面配置开启的情况下,这里设置多少次Full GC后,对老年代进行压缩

2.4、用64位操作系统

Linux下64位的jdk比32位jdk要慢一些,但是吃得内存更多,吞吐量更大。

2.5、XMX和XMS设置一样大

MaxPermSize和MinPermSize设置一样大,这样可以减轻伸缩堆大小带来的压力

2.6、使用CMS

1、 好处是用尽量少的年轻代,经验值是128M-256M,然后老年代利用CMS并行收集,这样能保证系统低延迟的吞吐效率;
2、 实际上cms的收集停顿时间非常的短,2G的内存,大约20-80ms的应用程序停顿时间;

2.7、系统停顿的时候可能是GC的问题也可能是程序的问题

多用jmap和jstack查看,或者killall -3 java,然后查看java控制台日志,能看出很多问题。

2.8、应用缓存

如果用了缓存,那么老年代应该大一些,缓存的HashMap不应该无限制长,建议采用LRU算法的Map做缓存,LRUMap的最大长度也要根据实际情况设定。

2.9、采用并发回收时

年轻代小一点,老年代要大,因为年老大用的是并发回收,即使时间长点也不会影响其他程序继续运行,网站不会停顿

2.10、JVM参数的设置

1、 特别是–Xmx–Xms–Xmn-XX:SurvivorRatio-XX:MaxTenuringThreshold等参数的设置没有一个固定的公式,需要根据PVold区实际数据YGC次数等多方面来衡量;
2、 为了避免promotionfaild可能会导致xmn设置偏小,也意味着YGC的次数会增多,处理并发访问的能力下降等问题;
3、 每个参数的调整都需要经过详细的性能测试,才能找到特定应用的最佳配置;

3、promotion failed

3.1、触发时机

年轻代GC时,eden与From中的存活对象存入To区时,to 区的空间不足,使用空间分配担保,再次晋升到老年代,而老年代空间不足,就会产生promontion faild,从而导致full gc。

3.2、造成影响

触发FGC

3.3、可能原因及方案

1、 Survivor空间过小;

1、 Survivor里的对象还不应该被移动到老年代,但年轻代又有很多对象需要放入Survivor;
2、 老年代空间小或者碎片多;

3.4、解决方案1:去掉Survivor空间

去掉Survivor空间,让老年代有足够的空间接纳来自年轻代的对象

1. 设置-XX:SurvivorRatio=65536
	1. 让Survivor空间无限小
2. 设置 -XX:MaxTenuringThreshold=0 
	1. 创建对象后,发生GC,存活的对象马上就去老年代
3. 设置CMSInitiatingOccupancyFraction为某个值(假设70)
	1. 这样老年代空间到70%时就开始执行CMS,老年代有足够的空间接纳来自年轻代的对象。

3.5、解决方案2: 方案1的升级版

上面方法不太好,因为没有用到Survivor空间,所以老年代容易满,CMS执行会比较频繁。
所以还是用Survivor空间,但是把Survivor空间加大,这样也不会有promotion failed。

为了解决暂停问题和promotion failed问题,最后我设置-XX:SurvivorRatio=1 ,并把MaxTenuringThreshold去掉,这样即没有暂停又不会有promotoin failed,而且更重要的是,老年代和永久代上升非常慢(因为好多对象到不了老年代就被回收了),所以CMS执行频率非常低,好几个小时才执行一次,这样,服务器都不用重启了。

对应的配置参数

-Xmx4000M -Xms4000M -Xmn600M -XX:PermSize=500M -XX:MaxPermSize=500M -Xss256K -XX:+DisableExplicitGC -XX:SurvivorRatio=1 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0 -XX:+CMSClassUnloadingEnabled -XX:LargePageSizeInBytes=128M -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=80 -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+PrintClassHistogram -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC -Xloggc:log/gc.log

CMSInitiatingOccupancyFraction值与Xmn的关系公式

3.6、分析promontion faild

年轻代GC时,eden与From中的存活对象存入To区时,to 区的空间不足,使用空间分配担保,再次晋升到老年代,而老年代空间不足,就会产生promontion faild,从而导致full gc。

3.6.1、推断1:不出现promontion faild

eden+from < 老年代 剩余内存时,不会出现promontion faild的情况。

3.6.2、推断2:不出现promontion faild

老年代 剩余内存

o=(Xmx-Xmn)*(1-CMSInitiatingOccupancyFraction/100)

年轻代可使用的空间:eden+from

y=Xmn-Xmn/(SurvivorRatior+2)

推断

y<o即

Xmn-Xmn/(SurvivorRatior+2) < (Xmx-Xmn)*(1-CMSInitiatingOccupancyFraction/100)

3.6.2、推断3:不出现promontion faild

 
CMSInitiatingOccupancyFraction <=100*((Xmx-Xmn)-(Xmn-Xmn/(SurvivorRatior+2)))/(Xmx-Xmn)

CMSInitiatingOccupancyFraction <=100*(老年代-年轻代)/年轻代)

案例:当xmx=128,xmn=36 SurvivorRatior=1时
CMSInitiatingOccupancyFraction <=((128.0-36)-(36-36/(1+2)))/(128-36)*100 =73.913

案例:当xmx=128 xmn=24 SurvivorRatior=1时
CMSInitiatingOccupancyFraction<=((128.0-24)-(24-24/(1+2)))/(128-24)*100=84.615…