您好,我是湘王,这是我的CSDN博客,欢迎您来,欢迎您再来~
上回说道如果当前Survivor区中年龄相同的一批对象总大小 ≥ Survivor总数 × 50%,那么这批对象及比它们年龄更大的对象,就都直接进入老年代。但是也有可能在Minor GC之后,发现剩余的存活对象太多,导致其大小总和超过Survivor区域,那么就会把这些对象直接转移到老年代,也不计算所谓的50%。
在JDK1.6以前,老年代空间分配担保流程是这样的:
而在JDK1.6之后,它做了些微调:
老年代的垃圾回收算法和年轻代不同,对老年代触发垃圾回收的时机是:
1、在Minor GC之前:清理老年代空间;
2、在Minor GC之后:剩余对象太多会导致老年代空间不足;
而老年代的标记整理算法比年轻代回收算法慢10倍,它是这么运行的:
1、标记出老年代当前存活的对象;
2、移动对象,避免碎片化;
3、回收垃圾对象。
JDK1.6之后,-XX:HandlePromotionFailure参数已废弃,且JDK1.8无法使用。所以在知道了老年代的GC之后,如果需要做JVM性能调优,那么需要做的就是:
1、尽可能让对象在年轻代中分配和回收;
2、要极力避免年轻代频繁地进垃圾回收。
一次数据计算的案例分享:
1、一套自研的分布式数据计算系统;
2、单台机器负责100次/分钟的数据提取和计算;
3、每次提取10000条数据到内存,计算耗费10秒/次;
4、每条数据包含20个字段,平均在1K大小,10000条=10MB;
5、4C8G;
6、JVM参数:-Xms4096M -Xmn1500M;
7、年轻代和老年代的内存空间都为1500MB。
那么,这种配置下的计算任务,它多久会填满JVM?
1、年轻代为1500M(8:1:1划分,Eden区1200M,Survivor1区150M,Survivor2区150M)
2、老年代为1500M;
3、每次计算分配10M,每分钟执行100次,Eden很快被填满;
4、假设每次Minor GC时还有20个计算任务无法结束,也就是说每次有200M的空间无法回收;
5、200M > Survivor(150M),Minor GC后进入老年代;
6、每次都走担保流程,7次之后,老年代空间也会被填满;
7、如此循环往复,每隔7~8分钟就执行一次Full GC。
那么结合实际业务场景,主要问题在于每次Survivor放不下存活对象。因此优化方式也比较简单直接:
1、增加年轻代内存比例(调至2048M),老年代适度降低;
2、Full GC从7~8分钟/次,降至几小时一次,大幅提升性能;
3、还可以通过调整-XX:SurvivorRatio=8这个默认值,实现:将Eden区比例适当调低,避免通过动态判断对象年龄而进入老年代。
常见的GC垃圾回收器(型号):
1、Serial和Serial Old:分别用来回收年轻代和老年代的垃圾对象;
2、ParNew:一般用在年轻代;
3、CMS:一般用在老年代;
4、G1:统一收集年轻代和老年代。
最后说一个GC中最恐怖的现象:STW(Stop the World,类似于《斗罗》中菊鬼斗罗的「两级静止领域」)。
当Full GC时,JVM会直接停止所有系统线程,系统卡顿,不接受也不处理任何请求,直到Full GC执行完毕。这种现象,就是Stop the World。有的STW比较短,几毫秒就完了。如果在负担不重的情况下,这没什么问题。但如果在双十一的秒杀系统中发生的话,那么......
FAQ:
1、如果每次Minor GC之后,另一块Survivor区可以放下全部存活对象,那么根本不会进入老年代,也就几乎不会引起Full GC,关键问题是如何让Survivor能放下全部存活对象;
2、JVM最大内存 * N = 栈内存 + 年轻代 + 方法区 * N(N为机器数量)
栈内存 = QPS估值 * 1M * 20(倍数);
年轻代 = 30分钟(估算) * 60 * QPS估值 * 接口内存估值;
方法区 = 200M~500M。
感谢您的大驾光临!咨询技术、产品、运营和管理相关问题,请关注后留言。欢迎骚扰,不胜荣幸~