内存分配与回收策略-实例

代码1-1 新生代 Minor GC

博客1

博客2

一、新生代 Minor GC

public class One {
    private static final int _1MB = 1024 * 1024;

    /**
     * VM参数:-XX:+UseSerialGC -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
    }

    public static void main(String[] args) {
        testAllocation();
    }
}
[GC (Allocation Failure) [DefNew: 6304K->639K(9216K), 0.0028333 secs] 6304K->4735K(19456K), 0.0028708 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 def new generation   total 9216K, used 7104K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  eden space 8192K,  78% used [0x00000000fec00000, 0x00000000ff250698, 0x00000000ff400000)
  from space 1024K,  62% used [0x00000000ff500000, 0x00000000ff59fd38, 0x00000000ff600000)
  to   space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
 tenured generation   total 10240K, used 4096K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
   the space 10240K,  40% used [0x00000000ff600000, 0x00000000ffa00020, 0x00000000ffa00200, 0x0000000100000000)
 Metaspace       used 3486K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 386K, capacity 388K, committed 512K, reserved 1048576K

这里有点问题感觉,不知道4MB的allocation4为什么被分配到了老年区。jdk版本是1.8的。

 

二、大对象直接进入老年代

public class Two {
    private static final int _1MB = 1024 * 1024;
    /*
     * VM参数:  -verbose:gc
                -Xms20M
                -Xmx20M
                -Xmn10M
                -XX:+PrintGCDetails
                -XX:SurvivorRatio=8
                -XX:+UseSerialGC
                -XX:PretenureSizeThreshold=3145728
     */
    public static void testPretenureSizeThreshold(){
        byte[] allocation;
        allocation = new byte[4 * _1MB];
    }

    public static void main(String[] args) {
        testPretenureSizeThreshold();
    }
}
Heap
 def new generation   total 9216K, used 2372K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  eden space 8192K,  28% used [0x00000000fec00000, 0x00000000fee51270, 0x00000000ff400000)
  from space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
  to   space 1024K,   0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
 tenured generation   total 10240K, used 4096K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
   the space 10240K,  40% used [0x00000000ff600000, 0x00000000ffa00010, 0x00000000ffa00200, 0x0000000100000000)
 Metaspace       used 3466K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 383K, capacity 388K, committed 512K, reserved 1048576K

 

三、长期存活的对象将进入老年区

/*-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+UseSerialGC -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=1 -XX:+PrintTenuringDistribution*/
public
class Three { private static final int _1MB = 1024 * 1024; @SuppressWarnings("unused") public static void testTenuringThreshold() { byte[] allocation1, allocation2, allocation3; allocation1 = new byte[_1MB / 4]; //0.25M allocation2 = new byte[4 * _1MB]; allocation3 = new byte[4 * _1MB]; allocation3 = null; allocation3 = new byte[4 * _1MB]; } public static void main(String[] args) { testTenuringThreshold(); } }
[GC (Allocation Failure) [DefNew
Desired survivor size 524288 bytes, new threshold 1 (max 1)
- age   1:     943224 bytes,     943224 total
: 6560K->921K(9216K), 0.0026489 secs] 6560K->5017K(19456K), 0.0026787 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [DefNew
Desired survivor size 524288 bytes, new threshold 1 (max 1)
: 5017K->0K(9216K), 0.0011371 secs] 9113K->5013K(19456K), 0.0011520 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 def new generation   total 9216K, used 4178K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  eden space 8192K,  51% used [0x00000000fec00000, 0x00000000ff014930, 0x00000000ff400000)
  from space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
  to   space 1024K,   0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
 tenured generation   total 10240K, used 5013K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
   the space 10240K,  48% used [0x00000000ff600000, 0x00000000ffae5498, 0x00000000ffae5600, 0x0000000100000000)
 Metaspace       used 3496K, capacity 4498K, committed 4864K, reserved 1056768K
  class space    used 387K, capacity 390K, committed 512K, reserved 1048576K

这里的结果和书上不一致,我觉得可能是jdk版本的问题。

 

四、动态年龄判定

public class Four {
    /**
     -verbose:gc
     -Xms20M
     -Xmx20M
     -Xmn10M
     -XX:+PrintGCDetails
     -XX:SurvivorRatio=8
     -XX:MaxTenuringThreshold=15
     -XX:+UseSerialGC
     -XX:+PrintTenuringDistribution
     **/
    private static final int _1MB = 1024*1024;

    @SuppressWarnings("unused")
    public static void testTenuringThreshold2(){
        byte[] allocation1, allocation2, allocation3, allocation4;
        allocation1 = new byte[_1MB / 4];
        //allocation1 + allocation2 等于survivor空间的一般
        allocation2 = new byte[_1MB / 4];
        allocation3 = new byte[4 * _1MB];
        allocation4 = new byte[4 * _1MB];   //eden区不够,发生第一次Minor GC

        allocation4 = null; //无GC Roots引用,下次Minor GC会回收掉4MB的内存
        allocation4 = new byte[4 * _1MB];
    }
    public static void main(String[] args) {
        testTenuringThreshold2();
    }
}
[GC (Allocation Failure) [DefNew
Desired survivor size 524288 bytes, new threshold 1 (max 15)
- age   1:    1048576 bytes,    1048576 total
: 6816K->1024K(9216K), 0.0031233 secs] 6816K->5247K(19456K), 0.0031555 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [DefNew
Desired survivor size 524288 bytes, new threshold 15 (max 15)
- age   1:       1688 bytes,       1688 total
: 5203K->1K(9216K), 0.0010271 secs] 9427K->5248K(19456K), 0.0010408 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 def new generation   total 9216K, used 4235K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  eden space 8192K,  51% used [0x00000000fec00000, 0x00000000ff0227f0, 0x00000000ff400000)
  from space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400698, 0x00000000ff500000)
  to   space 1024K,   0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
 tenured generation   total 10240K, used 5247K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
   the space 10240K,  51% used [0x00000000ff600000, 0x00000000ffb1fcd0, 0x00000000ffb1fe00, 0x0000000100000000)
 Metaspace       used 3492K, capacity 4498K, committed 4864K, reserved 1056768K
  class space    used 387K, capacity 390K, committed 512K, reserved 1048576K

 

 

关于垃圾分代收集的一些思考

现有的垃圾收集器一般采用的分代收集的思想。将整个Java堆分为新生代Young和老年代Old,而新生代又分为Eden,Survivor From,Survivor To(比例为8:1:1)。

 

首先考虑第一个问题:为什么要有Survivor区?

新生代采用的是复制算法来回收垃圾,如果没有Survivor区,Eden区每进行一次Minor GC,存活的对象就会复制到老年代,这样老年代很快就被填满了,然后出发Major GC。而老年代的内存一般大于新生代,进行一次Major GC消耗的时间比Minor GC长得多,会严重影响程序的执行效率。

 

为什么要设置两个Survivor区?

设置两个Survivor区最大的好处就是解决了碎片化。

下面看一个例子:

新建的对象在Eden区中,一旦Eden区满了,触发一次Minor GC(Eden和Survivor From会被清空),Eden区中的存活对象就会被移动到Survivor区。而等到下一次Eden区满的时候,又要进行一次Minor GC,而Eden和Survivor各有一些存活对象,如果此时把Eden区的存活对象硬放到Survivor区,很明显这两部分对象所占有的内存是不连续的,也就导致了内存碎片化,如下图:

建立两块Survivor区,刚刚新建的对象在Eden中,经历一次Minor GC,Eden中的存活对象就会被移动到第一块Survivor Space “From”,Eden被清空;等Eden区再满了,就再触发一次Minor GC(Eden和Survivor From会被清空),Eden和Survivor From中的存活对象又会被复制送入第二块Survivor To(这个过程非常重要,因为这种复制算法保证了To中来自From和Eden两部分的存活对象占用连续的内存空间,避免了碎片化的发生)。From和Eden被清空,然后下一轮From与To交换角色,如此循环往复。如果对象的年龄达到15,该对象就会被送到老年代中。

  

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

posted @ 2020-12-02 18:58  Peterxiazhen  阅读(160)  评论(0编辑  收藏  举报