新生代串行收集器对内存分配及回收的影响

1. 通常情况下,对象在eden中分配。当eden无法分配时,触发一次Minor GC。 

public class YoungGenGc
{
    private static final int _1MB = 1024 * 1024;
    public static void main( String[] args )
    {
        testAllocation();
    }
    /**
     * VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
     */
    public static void testAllocation()
    {
        byte[] allocation1, allocation2, allocation3, allocation4;
        allocation1 = new byte[ 2 * _1MB ];
        allocation2 = new byte[ 2 * _1MB ];
        allocation3 = new byte[ 2 * _1MB ];
        allocation4 = new byte[ 4 * _1MB ];//出现一次 Minor GC

    }
}

testAllocation()方法输出结果 

[GC [DefNew: 6651K->148K(9216K), 0.0070106 secs] 6651K->6292K(19456K), 0.0070426 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap 
def new generation   total 9216K, used 4326K [0x029d0000, 0x033d0000, 0x033d0000) 
  eden space 8192K,  51% used [0x029d0000, 0x02de4828, 0x031d0000) 
  from space 1024K,  14% used [0x032d0000, 0x032f5370, 0x033d0000) 
  to   space 1024K,   0% used [0x031d0000, 0x031d0000, 0x032d0000) 
tenured generation   total 10240K, used 6144K [0x033d0000, 0x03dd0000, 0x03dd0000) 
   the space 10240K,  60% used [0x033d0000, 0x039d0030, 0x039d0200, 0x03dd0000) 
compacting perm gen  total 12288K, used 2114K [0x03dd0000, 0x049d0000, 0x07dd0000) 
   the space 12288K,  17% used [0x03dd0000, 0x03fe0998, 0x03fe0a00, 0x049d0000) 
No shared spaces configured.

执行testAllocation()方法后输出了GC日志以及内存分配状况。-Xms20M -Xmx20M -Xmn10M这3个参数确定了Java堆大小为20M,不可扩展,其中10M分配给新生代,剩下的10M即为老年代。-XX:SurvivorRatio=8决定了新生代中eden与survivor的空间比例是1:8,从输出的结果也清晰的看到“eden space 8192K、from space 1024K、to space 1024K”的信息,新生代总可用空间为9216K(eden+1个survivor)。 

  我们也注意到在执行testAllocation()时出现了一次Minor GC,GC的结果是新生代6651K变为148K,而总占用内存则几乎没有减少(因为几乎没有可回收的对象)。这次GC是发生的原因是为allocation4分配内存的时候,eden已经被占用了6M,剩余空间已不足分配allocation4所需的4M内存,因此发生Minor GC。GC期间虚拟机发现已有的3个2M大小的对象全部无法放入survivor空间(survivor空间只有1M大小),所以直接转移到老年代去。GC后4M的allocation4对象分配在eden中。 

2. 配置了PretenureSizeThreshold的情况下,对象大于设置值将直接在老年代分配

/**
     * VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
     * -XX:PretenureSizeThreshold=3145728
     */
    public static void testPretenureSizeThreshold()
    {
        byte[] allocation;
        allocation = new byte[ 4 * _1MB ];//直接分配在老年代中
    }

testPretenureSizeThreshold()方法输出:

Heap 
def new generation   total 9216K, used 671K [0x029d0000, 0x033d0000, 0x033d0000) 
  eden space 8192K,   8% used [0x029d0000, 0x02a77e98, 0x031d0000) 
  from space 1024K,   0% used [0x031d0000, 0x031d0000, 0x032d0000) 
  to   space 1024K,   0% used [0x032d0000, 0x032d0000, 0x033d0000) 
tenured generation   total 10240K, used 4096K [0x033d0000, 0x03dd0000, 0x03dd0000) 
   the space 10240K,  40% used [0x033d0000, 0x037d0010, 0x037d0200, 0x03dd0000) 
compacting perm gen  total 12288K, used 2107K [0x03dd0000, 0x049d0000, 0x07dd0000) 
   the space 12288K,  17% used [0x03dd0000, 0x03fdefd0, 0x03fdf000, 0x049d0000) 
No shared spaces configured.

执行testPretenureSizeThreshold()方法后,我们看到eden空间几乎没有被使用,而老年代的10M控件被使用了40%,也就是4M的allocation对象直接就分配在老年代中,则是因为PretenureSizeThreshold被设置为3M,因此超过3M的对象都会直接从老年代分配。 

3.在eden经过GC后存活,并且survivor能容纳的对象,将移动到survivor空间内,如果对象在survivor中继续熬过若干次回收(默认为15次)将会被移动到老年代中。回收次数由MaxTenuringThreshold设置。 
  分别以-XX:MaxTenuringThreshold=1和-XX:MaxTenuringThreshold=15两种设置来执行testTenuringThreshold(),方法中allocation1对象需要256K内存,survivor空间可以容纳。当MaxTenuringThreshold=1时,allocation1对象在第二次GC发生时进入老年代,新生代已使用的内存GC后非常干净的变成0KB。而MaxTenuringThreshold=15时,第二次GC发生后,allocation1对象则还留在新生代survivor空间,这时候新生代仍然有404KB被占用。 

/**
     * VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=1
     * -XX:+PrintTenuringDistribution
     */
    @SuppressWarnings( "unused" )
    public static void testTenuringThreshold()
    {
        byte[] allocation1, allocation2, allocation3;
        allocation1 = new byte[ _1MB / 4 ]; // 什么时候进入老年代决定于XX:MaxTenuringThreshold设置  
        allocation2 = new byte[ 4 * _1MB ]; allocation3 = new byte[ 4 * _1MB ];
        allocation3 = null;
        allocation3 = new byte[ 4 * _1MB ];
}

输出MaxTenuringThreshold=1 

[GC [DefNew 
Desired survivor size 524288 bytes, new threshold 1 (max 1) 
- age   1:     414664 bytes,     414664 total 
: 4859K->404K(9216K), 0.0065012 secs] 4859K->4500K(19456K), 0.0065283 secs] [Times: user=0.02 sys=0.00, real=0.02 secs] 
[GC [DefNew 
Desired survivor size 524288 bytes, new threshold 1 (max 1) 
: 4500K->0K(9216K), 0.0009253 secs] 8596K->4500K(19456K), 0.0009458 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap 
def new generation   total 9216K, used 4178K [0x029d0000, 0x033d0000, 0x033d0000) 
  eden space 8192K,  51% used [0x029d0000, 0x02de4828, 0x031d0000) 
  from space 1024K,   0% used [0x031d0000, 0x031d0000, 0x032d0000) 
  to   space 1024K,   0% used [0x032d0000, 0x032d0000, 0x033d0000) 
tenured generation   total 10240K, used 4500K [0x033d0000, 0x03dd0000, 0x03dd0000) 
   the space 10240K,  43% used [0x033d0000, 0x03835348, 0x03835400, 0x03dd0000) 
compacting perm gen  total 12288K, used 2114K [0x03dd0000, 0x049d0000, 0x07dd0000) 
   the space 12288K,  17% used [0x03dd0000, 0x03fe0998, 0x03fe0a00, 0x049d0000)

MaxTenuringThreshold=1 5

[GC [DefNew 
Desired survivor size 524288 bytes, new threshold 15 (max 15) 
- age   1:     414664 bytes,     414664 total 
: 4859K->404K(9216K), 0.0049637 secs] 4859K->4500K(19456K), 0.0049932 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC [DefNew 
Desired survivor size 524288 bytes, new threshold 15 (max 15) 
- age   2:     414520 bytes,     414520 total 
: 4500K->404K(9216K), 0.0008091 secs] 8596K->4500K(19456K), 0.0008305 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap 
def new generation   total 9216K, used 4582K [0x029d0000, 0x033d0000, 0x033d0000) 
  eden space 8192K,  51% used [0x029d0000, 0x02de4828, 0x031d0000) 
  from space 1024K,  39% used [0x031d0000, 0x03235338, 0x032d0000) 
  to   space 1024K,   0% used [0x032d0000, 0x032d0000, 0x033d0000) 
tenured generation   total 10240K, used 4096K [0x033d0000, 0x03dd0000, 0x03dd0000) 
   the space 10240K,  40% used [0x033d0000, 0x037d0010, 0x037d0200, 0x03dd0000) 
compacting perm gen  total 12288K, used 2114K [0x03dd0000, 0x049d0000, 0x07dd0000) 
   the space 12288K,  17% used [0x03dd0000, 0x03fe0998, 0x03fe0a00, 0x049d0000)

4. 如果在survivor空间中相同年龄所有对象大小的累计值大于survivor空间的一半,大于或等于各年龄的对象就可以直接进入老年代,无需达到MaxTenuringThreshold中要求的年龄。 

/**
     * VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15
     * -XX:+PrintTenuringDistribution
     */
    public static void testTenuringThreshold2()
    {
        byte[] allocation1, allocation2, allocation3, allocation4;
        allocation1 = new byte[ _1MB / 4 ]; // allocation1+allocation2大于survivo空间一半  
        allocation2 = new byte[ _1MB / 4 ];
        allocation3 = new byte[ 4 * _1MB ];
        allocation4 = new byte[ 4 * _1MB ];
        allocation4 = null;
        allocation4 = new byte[ 4 * _1MB ];
    }

testTenuringThreshold2() 输出

[GC [DefNew 
Desired survivor size 524288 bytes, new threshold 1 (max 15) 
- age   1:     676824 bytes,     676824 total 
: 5115K->660K(9216K), 0.0050136 secs] 5115K->4756K(19456K), 0.0050443 secs] [Times: user=0.00 sys=0.01, real=0.01 secs] 
[GC [DefNew 
Desired survivor size 524288 bytes, new threshold 15 (max 15) 
: 4756K->0K(9216K), 0.0010571 secs] 8852K->4756K(19456K), 0.0011009 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap 
def new generation   total 9216K, used 4178K [0x029d0000, 0x033d0000, 0x033d0000) 
  eden space 8192K,  51% used [0x029d0000, 0x02de4828, 0x031d0000) 
  from space 1024K,   0% used [0x031d0000, 0x031d0000, 0x032d0000) 
  to   space 1024K,   0% used [0x032d0000, 0x032d0000, 0x033d0000) 
tenured generation   total 10240K, used 4756K [0x033d0000, 0x03dd0000, 0x03dd0000) 
   the space 10240K,  46% used [0x033d0000, 0x038753e8, 0x03875400, 0x03dd0000) 
compacting perm gen  total 12288K, used 2114K [0x03dd0000, 0x049d0000, 0x07dd0000) 
   the space 12288K,  17% used [0x03dd0000, 0x03fe09a0, 0x03fe0a00, 0x049d0000) 
No shared spaces configured.

执行testTenuringThreshold2()方法,并将设置-XX:MaxTenuringThreshold=15,发现运行结果中survivor占用仍然为0%,而老年代比预期增加了6%,也就是说allocation1、allocation2对象都直接进入了老年代,而没有等待到15岁的临界年龄。因为这2个对象加起来已经到达了512K,并且它们是同年的,满足同年对象达到survivor空间的一半规则。我们只要注释掉其中一个对象new操作,就会发现另外一个就不会晋升到老年代中去了。 

5. 在Minor GC触发时,会检测之前每次晋升到老年代的平均大小是否大于老年代的剩余空间,如果大于,改为直接进行一次Full GC,如果小于则查看HandlePromotionFailure设置看看是否允许担保失败,如果允许,那仍然进行Minor GC,如果不允许,则也要改为进行一次Full GC。 

/**
     * VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:-HandlePromotionFailure
     */
    public static void testHandlePromotion()
    {
        byte[] allocation1, allocation2, allocation3, allocation4, allocation5, allocation6, allocation7;
        allocation1 = new byte[ 2 * _1MB ];
        allocation2 = new byte[ 2 * _1MB ];
        allocation3 = new byte[ 2 * _1MB ];
        allocation1 = null;
        allocation4 = new byte[ 2 * _1MB ];
        allocation5 = new byte[ 2 * _1MB ];
        allocation6 = new byte[ 2 * _1MB ];
        allocation4 = null;
        allocation5 = null;
        allocation6 = null;
        allocation7 = new byte[ 2 * _1MB ];
}

testHandlePromotion() 输出:

HandlePromotionFailure = false 

[GC [DefNew: 6651K->148K(9216K), 0.0078936 secs] 6651K->4244K(19456K), 0.0079192 secs] [Times: user=0.00 sys=0.02, real=0.02 secs] 
[GC [DefNew: 6378K->6378K(9216K), 0.0000206 secs][Tenured: 4096K->4244K(10240K), 0.0042901 secs] 10474K->4244K(19456K), [Perm : 2104K->2104K(12288K)], 0.0043613 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 

HandlePromotionFailure = true 

[GC [DefNew: 6651K->148K(9216K), 0.0054913 secs] 6651K->4244K(19456K), 0.0055327 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC [DefNew: 6378K->148K(9216K), 0.0006584 secs] 10474K->4244K(19456K), 0.0006857 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

前面提到,新生代才有复制收集算法,但为了内存利用率,只使用其中一个survivor空间来作为轮换备份,因此当出现大量对象在GC后仍然存活的情况(最极端就是GC后所有对象都存活),就需要老年代进行分配担保,把survivor无法容纳的对象直接放入老年代。与生活中贷款担保类似,老年代要进行这样的担保,前提就是老年代本身还有容纳这些对象的剩余空间,一共有多少对象在GC之前是无法明确知道的,所以取之前每一次GC晋升到老年代对象容量的平均值与老年代的剩余空间进行比较决定是否进行Full GC来让老年代腾出更多空间。 


  取平均值进行比较其实仍然是一种动态概率的手段,也就是说如果某次Minor GC存活后的对象突增,大大高于平均值的话,依然会导致担保失败,这样就只好在失败后重新进行一次Full GC。虽然担保失败时做的绕的圈子是最大的,但大部分情况下都还是会将HandlePromotionFailure打开,避免Full GC过于频繁。 


你可能感兴趣的:(新生代串行收集器对内存分配及回收的影响)