实战:内存分配与回收策略

在阅读《深入理解Java虚拟机:JVM高级特性与最佳实践(第3版) (华章原创精品) - 周志明》一书中,为增强理解,跟着实践有感。

书中第3.8.1节:对象优先在Eden分配,实验结果如下:

![](https://upload-images.jianshu.io/upload_images/15296782-c6e2259230b13dfe.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


然后我得到的不一样的结果(开始质疑书本的正确性),后来想到可能是因为JDK版本不同,使用的垃圾收集器不同。所以后面特意指定了垃圾收集器-XX:+UseSerialGC就与书上结果一致了。
为避免其他因素干扰,我在测试前使用了System.gz()强制JVM执行一次full gc。
```java
private static final int _1MB = 1024 *1024;
    /**
     * VM参数:-XX:+UseSerialGC -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
     * 注意不同的收集器内存分配和回收策略不一样,本例使用-XX:+UseSerialGC,虚拟机运行在Client模式下的默认值,Serial+Serial Old。得到的结果和书上一致
     */
    public static void testAllocation() {
        System.gc();
        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
    }
```
下面展示每一步的内存分配和回收情况,可看到初始直接执行full gc后也并不能完全回收年轻代和老年代,会有一小部分空间被占用。然后allocation1 在eden区成功分配了2MB,allocation2,allocation3也成功分配,至此eden区已有6MB。

> 根据我们给JVM设置的参数,堆内存20MB,不可扩展(初始内存和最大内存都为20MB),年轻代10MB(可得老年代也为10MB),eden区和survivor区的比例为8(即8:1:1,survivor分from和to),所以eden区有8MB,survivor区1M,年轻代可用空间为9MB(年轻代采用的标记-复制算法,eden+survivor中的存活对象复制到另一个survivor)。

年轻代还剩不到2MB,不足以分配allocation4,因此触发了GC(原因是内存分配失败),而目前的6MB对象都还存活,无法回收,只好通过分配担保机制提前转移到老年代,所以老年代变成了6MB,然后年轻代分配了allocation4,变为4MB。

```java
/**
        System.gc();
//        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
*/
[Full GC (System.gc()) [Tenured: 0K->724K(10240K), 0.0024904 secs] 2162K->724K(19456K), [Metaspace: 3426K->3426K(1056768K)], 0.0025292 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 def new generation   total 9216K, used 246K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  eden space 8192K,   3% used [0x00000000fec00000, 0x00000000fec3d890, 0x00000000ff400000)
  from space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
  to   space 1024K,   0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
 tenured generation   total 10240K, used 724K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
   the space 10240K,   7% used [0x00000000ff600000, 0x00000000ff6b5240, 0x00000000ff6b5400, 0x0000000100000000)
 Metaspace       used 3434K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 374K, capacity 388K, committed 512K, reserved 1048576K

/**
        System.gc();
        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
*/
[Full GC (System.gc()) [Tenured: 0K->713K(10240K), 0.0021630 secs] 1994K->713K(19456K), [Metaspace: 3295K->3295K(1056768K)], 0.0022069 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 def new generation   total 9216K, used 2458K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  eden space 8192K,  30% used [0x00000000fec00000, 0x00000000fee66858, 0x00000000ff400000)
  from space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
  to   space 1024K,   0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
 tenured generation   total 10240K, used 713K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
   the space 10240K,   6% used [0x00000000ff600000, 0x00000000ff6b2600, 0x00000000ff6b2600, 0x0000000100000000)
 Metaspace       used 3318K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 362K, capacity 388K, committed 512K, reserved 1048576K

/**
        System.gc();
        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
*/
[Full GC (System.gc()) [Tenured: 0K->710K(10240K), 0.0018954 secs] 1994K->710K(19456K), [Metaspace: 3221K->3221K(1056768K)], 0.0019325 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 def new generation   total 9216K, used 4506K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  eden space 8192K,  55% used [0x00000000fec00000, 0x00000000ff066808, 0x00000000ff400000)
  from space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
  to   space 1024K,   0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
 tenured generation   total 10240K, used 710K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
   the space 10240K,   6% used [0x00000000ff600000, 0x00000000ff6b1920, 0x00000000ff6b1a00, 0x0000000100000000)
 Metaspace       used 3363K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 369K, capacity 388K, committed 512K, reserved 1048576K

/**
        System.gc();
        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
*/
[Full GC (System.gc()) [Tenured: 0K->710K(10240K), 0.0020691 secs] 1994K->710K(19456K), [Metaspace: 3192K->3192K(1056768K)], 0.0021076 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 def new generation   total 9216K, used 6554K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  eden space 8192K,  80% used [0x00000000fec00000, 0x00000000ff266818, 0x00000000ff400000)
  from space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
  to   space 1024K,   0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
 tenured generation   total 10240K, used 710K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
   the space 10240K,   6% used [0x00000000ff600000, 0x00000000ff6b1828, 0x00000000ff6b1a00, 0x0000000100000000)
 Metaspace       used 3205K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 351K, capacity 388K, committed 512K, reserved 1048576K

/**
        System.gc();
        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
*/
[Full GC (System.gc()) [Tenured: 0K->724K(10240K), 0.0027738 secs] 2162K->724K(19456K), [Metaspace: 3421K->3421K(1056768K)], 0.0028359 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [DefNew: 6471K->25K(9216K), 0.0022628 secs] 7196K->6893K(19456K), 0.0022878 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 def new generation   total 9216K, used 4203K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  eden space 8192K,  51% used [0x00000000fec00000, 0x00000000ff014930, 0x00000000ff400000)
  from space 1024K,   2% used [0x00000000ff500000, 0x00000000ff5064f8, 0x00000000ff600000)
  to   space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
 tenured generation   total 10240K, used 6868K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
   the space 10240K,  67% used [0x00000000ff600000, 0x00000000ffcb5138, 0x00000000ffcb5200, 0x0000000100000000)
 Metaspace       used 3447K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 376K, capacity 388K, committed 512K, reserved 1048576K
```


下面再来验证下JDK8默认的垃圾收集器,首先CMD命令查看使用的是哪种收集器
```java
java -XX:+PrintCommandLineFlags -version

-XX:InitialHeapSize=267464320 -XX:MaxHeapSize=4279429120 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
java version "1.8.0_171"
Java(TM) SE Runtime Environment (build 1.8.0_171-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.171-b11, mixed mode)
```
UseParallelGC 即 Parallel Scavenge + Parallel Old,再查看详细信息
```java
java -XX:+PrintGCDetails -version

java version "1.8.0_171"
Java(TM) SE Runtime Environment (build 1.8.0_171-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.171-b11, mixed mode)
Heap
 PSYoungGen      total 76288K, used 2621K [0x000000076af80000, 0x0000000770480000, 0x00000007c0000000)
  eden space 65536K, 4% used [0x000000076af80000,0x000000076b20f748,0x000000076ef80000)
  from space 10752K, 0% used [0x000000076fa00000,0x000000076fa00000,0x0000000770480000)
  to   space 10752K, 0% used [0x000000076ef80000,0x000000076ef80000,0x000000076fa00000)
 ParOldGen       total 175104K, used 0K [0x00000006c0e00000, 0x00000006cb900000, 0x000000076af80000)
  object space 175104K, 0% used [0x00000006c0e00000,0x00000006c0e00000,0x00000006cb900000)
 Metaspace       used 2351K, capacity 4480K, committed 4480K, reserved 1056768K
  class space    used 254K, capacity 384K, committed 384K, reserved 1048576K
```

我们将对应参数改为-XX:+UseParallelGC,再来看一下结果:
allocation1, allocation2,allocation3可直接分配,与上文一致,不同的是allocation4,直接被分到了老年代(用的应该是大对象直接进入老年代的分配规则),由此可知不同收集器的实现方式是有差异的。

```java
/**
        System.gc();
//        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
*/
[GC (System.gc()) [PSYoungGen: 1994K->776K(9216K)] 1994K->784K(19456K), 0.0006403 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (System.gc()) [PSYoungGen: 776K->0K(9216K)] [ParOldGen: 8K->705K(10240K)] 784K->705K(19456K), [Metaspace: 3174K->3174K(1056768K)], 0.0031807 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
Heap
 PSYoungGen      total 9216K, used 246K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
  eden space 8192K, 3% used [0x00000000ff600000,0x00000000ff63d890,0x00000000ffe00000)
  from space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
  to   space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
 ParOldGen       total 10240K, used 705K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  object space 10240K, 6% used [0x00000000fec00000,0x00000000fecb0560,0x00000000ff600000)
 Metaspace       used 3185K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 350K, capacity 388K, committed 512K, reserved 1048576K

/**
        System.gc();
        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
*/
[GC (System.gc()) [PSYoungGen: 2162K->808K(9216K)] 2162K->816K(19456K), 0.0015479 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (System.gc()) [PSYoungGen: 808K->0K(9216K)] [ParOldGen: 8K->716K(10240K)] 816K->716K(19456K), [Metaspace: 3339K->3339K(1056768K)], 0.0030067 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
Heap
 PSYoungGen      total 9216K, used 2457K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
  eden space 8192K, 30% used [0x00000000ff600000,0x00000000ff8667f8,0x00000000ffe00000)
  from space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
  to   space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
 ParOldGen       total 10240K, used 716K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  object space 10240K, 7% used [0x00000000fec00000,0x00000000fecb33a8,0x00000000ff600000)
 Metaspace       used 3362K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 369K, capacity 388K, committed 512K, reserved 1048576K

/**
        System.gc();
        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
*/
[GC (System.gc()) [PSYoungGen: 1994K->840K(9216K)] 1994K->848K(19456K), 0.0007088 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (System.gc()) [PSYoungGen: 840K->0K(9216K)] [ParOldGen: 8K->711K(10240K)] 848K->711K(19456K), [Metaspace: 3257K->3257K(1056768K)], 0.0030776 secs] [Times: user=0.06 sys=0.00, real=0.00 secs] 
Heap
 PSYoungGen      total 9216K, used 4506K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
  eden space 8192K, 55% used [0x00000000ff600000,0x00000000ffa66808,0x00000000ffe00000)
  from space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
  to   space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
 ParOldGen       total 10240K, used 711K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  object space 10240K, 6% used [0x00000000fec00000,0x00000000fecb1ce0,0x00000000ff600000)
 Metaspace       used 3337K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 363K, capacity 388K, committed 512K, reserved 1048576K

/**
        System.gc();
        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
*/
[GC (System.gc()) [PSYoungGen: 1994K->840K(9216K)] 1994K->848K(19456K), 0.0006964 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (System.gc()) [PSYoungGen: 840K->0K(9216K)] [ParOldGen: 8K->710K(10240K)] 848K->710K(19456K), [Metaspace: 3245K->3245K(1056768K)], 0.0028236 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 PSYoungGen      total 9216K, used 6554K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
  eden space 8192K, 80% used [0x00000000ff600000,0x00000000ffc66878,0x00000000ffe00000)
  from space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
  to   space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
 ParOldGen       total 10240K, used 710K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  object space 10240K, 6% used [0x00000000fec00000,0x00000000fecb1ac8,0x00000000ff600000)
 Metaspace       used 3302K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 361K, capacity 388K, committed 512K, reserved 1048576K

/**
        System.gc();
        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
*/
[GC (System.gc()) [PSYoungGen: 1994K->792K(9216K)] 1994K->800K(19456K), 0.0013828 secs] [Times: user=0.02 sys=0.00, real=0.00 secs] 
[Full GC (System.gc()) [PSYoungGen: 792K->0K(9216K)] [ParOldGen: 8K->711K(10240K)] 800K->711K(19456K), [Metaspace: 3259K->3259K(1056768K)], 0.0031560 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 PSYoungGen      total 9216K, used 6570K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
  eden space 8192K, 80% used [0x00000000ff600000,0x00000000ffc6a828,0x00000000ffe00000)
  from space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
  to   space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
 ParOldGen       total 10240K, used 4807K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  object space 10240K, 46% used [0x00000000fec00000,0x00000000ff0b1d60,0x00000000ff600000)
 Metaspace       used 3447K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 376K, capacity 388K, committed 512K, reserved 1048576K
```

上一篇:JVM-GC日志详细分析


下一篇:元素中显示n行文字且超出隐藏显示省略号