JVM之内存分配策略

108 阅读3分钟

1、内存分配策略

常用参数:

-verbose:gc  //打印GC
-XX:+PrintGCDetails  //打印GC详情
-Xms20M //堆的最小值
-Xmx20M  //堆的最大值
-Xmn10M  //新生代的大小
-XX:+UseSerialGC //指定收集器,这里使用Serial收集器,前面已经说过对于客户端JVM会默认使用Serial收集器,而对于服务端默认会使用Parallel收集器,可以通过java -version判断是客户端还是服务端,JVM是判断单核CPU,内存小于1G的为客户端,但是现在基本不存在,所以基本上都是Parallel收集器,如下图
//-XX:+UseParallelGC //指定Parallel Scavenge收集器
-XX:SurvivorRatio=8  //定义了Eden和Survivor的比例,默认为8
-XX:+PrintTenuringDistribution //打印对象年龄

image-20201125235542777

1、优先分配到Eden

当一个对象分配的时候,如果Eden区存在连续的空间分配给这个对象,那么就会首先分配在Eden区;

案例:

image-20210120111439981

2、大对象直接分配到老年代

通过一个参数来指定大对象的阈值:

-XX:PretenureSizeThreshold=5M //那么凡是>=7M的对象都会直接分配到老年代

这么做也有一个好处就是:因为新生代都是采用的复制算法,如果存在大对象,会导致复制效率很低;

案例:

image-20210120111759062

3、长期存活的对象分配到老年代

这种情况JVM是如何判断某个对象的年龄的呢?

在JDK6及其以前,认为在YOUNG GC的情况下,存活了15次,则进入老年代;但是在JDK6之后,并不是严格的到达这个年龄之后才会进入老年代;

所谓的年龄计算,是每次触发YOUNG GC,进行一次复制,则AGE+1,当AGE达到了阈值就会进入老年代;

也可以通过一个参数来指定年龄的阈值:

-XX:MaxTenuringThreshold=2  //那么凡是age>=2的都会进入老年代

案例:

image-20210120113826508

4、空间分配担保

所谓的空间分配担保,就是触发YOUNG GC之后,Survivor放不下存活的对象,则向老年代申请空间;

在JDK7以前,在发生Young GC之前,虚拟机会检查老年代最大可用的连续空间是否大于新生代所有对象的总空间:

  • 如果大于,则正常触发Young GC;

  • 如果小于,则虚拟机会通过参数-XX:HandlePromotionFailure判断是否允许内存担保失败

    • 如果是,则会继续判断老年代最大可用连续空间是否大于历次晋升到老年代的对象的平台大小;
      • 如果大于,则进行Young GC,但此次Young GC是有风险的,可能会导致OOM;
      • 如果小于,则触发Full GC,然后再进行Young GC;
    • 如果否,则直接进行Full GC;
    +XX:HandlePromotionFailure //+:启用,-:禁用
    

在JDK7以后,参数-XX:HandlePromotionFailure就无效了,只要老年代的连续空间大于新生代对象总大小或这历次晋升的平均大小就会进行Young GC,否则进行Full GC,其实就是少了一步参数判断,整体过程还是和JDK7以前是一样的;

image-20210121150150885 image-20210121151923038

案例:

image-20210120114059763

image-20210120114521606

5、动态对象年龄判断

虚拟机并不总是永远都要求对象年龄必须到达MaxTenuringThreshold才能晋升到老年代,如果在Survivor区相同年龄的所有对象大小总和大于Survivor空间的一半时,年龄大于或等于该年龄的对象直接进行老年代,无需等待MaxTenuringThreshold设置的年龄阈值;

案例:

image-20210121144421888

image-20210121144545452