垃圾收集机制与算法

524 阅读9分钟

一、JVM内存分配与回收


1、Minor GC和Full GC 有什么不同

Minor GC/Young GC指发生新生代的的垃圾收集动作,Minor GC非常频繁,回收速度一般也比较快。

Major GC/Full GC:一般会回收老年代,年轻代,方法区的垃圾, Major GC的速度一般会比Minor GC的慢10倍以上

2、对象优先在Eden区分配

大多数情况下,对象在新生代中Eden区分配。当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC。

// 添加运行JVM参数:‐XX:+PrintGCDetails
public class GCTest {
    public static void main(String[] args) throws InterruptedException {
    }
}

运行结果如下:

Heap
 PSYoungGen      total 38400K, used 4662K [0x00000000d5d80000, 0x00000000d8800000, 0x0000000100000000)
  eden space 33280K, 14% used [0x00000000d5d80000,0x00000000d620d8f0,0x00000000d7e00000)
  from space 5120K, 0% used [0x00000000d8300000,0x00000000d8300000,0x00000000d8800000)
  to   space 5120K, 0% used [0x00000000d7e00000,0x00000000d7e00000,0x00000000d8300000)
 ParOldGen       total 87552K, used 0K [0x0000000081800000, 0x0000000086d80000, 0x00000000d5d80000)
  object space 87552K, 0% used [0x0000000081800000,0x0000000081800000,0x0000000086d80000)
 Metaspace       used 3484K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 381K, capacity 388K, committed 512K, reserved 1048576K

结论: 即使程序什么也没做,新生代也会使用至少几兆内存。

// 添加运行JVM参数:‐XX:+PrintGCDetails
public class GCTest {
    public static void main(String[] args) throws InterruptedException {
        byte[] byte1 = new byte[25000*1024];
    }
}

运行结果如下:

Heap
 PSYoungGen      total 38400K, used 29662K [0x00000000d5d80000, 0x00000000d8800000, 0x0000000100000000)
  eden space 33280K, 89% used [0x00000000d5d80000,0x00000000d7a77900,0x00000000d7e00000)
  from space 5120K, 0% used [0x00000000d8300000,0x00000000d8300000,0x00000000d8800000)
  to   space 5120K, 0% used [0x00000000d7e00000,0x00000000d7e00000,0x00000000d8300000)
 ParOldGen       total 87552K, used 0K [0x0000000081800000, 0x0000000086d80000, 0x00000000d5d80000)
  object space 87552K, 0% used [0x0000000081800000,0x0000000081800000,0x0000000086d80000)
 Metaspace       used 3484K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 381K, capacity 388K, committed 512K, reserved 1048576K

结论: 对象优先分配在新生代中Eden区(前提条件是Eden区能够容纳被分配对象)

public class GCTest {
    public static void main(String[] args) throws InterruptedException {
        byte[] byte1,byte2;
        byte1 = new byte[25000*1024];
        byte2 = new byte[10000*1024];
    }
}

运行结果如下:

[GC (Allocation Failure) [PSYoungGen: 28996K->1000K(38400K)] 28996K->26000K(125952K), 0.0959246 secs] [Times: user=0.09 sys=0.00, real=0.10 secs] 
Heap
 PSYoungGen      total 38400K, used 11666K [0x00000000d5d80000, 0x00000000da880000, 0x0000000100000000)
  eden space 33280K, 32% used [0x00000000d5d80000,0x00000000d67ea800,0x00000000d7e00000)
  from space 5120K, 19% used [0x00000000d7e00000,0x00000000d7efa020,0x00000000d8300000)
  to   space 5120K, 0% used [0x00000000da380000,0x00000000da380000,0x00000000da880000)
 ParOldGen       total 87552K, used 25000K [0x0000000081800000, 0x0000000086d80000, 0x00000000d5d80000)
  object space 87552K, 28% used [0x0000000081800000,0x000000008306a010,0x0000000086d80000)
 Metaspace       used 3203K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 350K, capacity 388K, committed 512K, reserved 1048576K

结论: 给byte2分配内存的时候,Eden区没有足够空间进行分配,虚拟机将会发起一次Minor GC,GC期间虚拟机又发现byte1无法存入Survior空间,所以只好把新生代中存活的对象部分转移到老年代,部分可能还会放在Survivor区,老年代上的空间足够存放byte1,所以不会出现Full GC

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

大对象就是需要大量连续内存空间的对象(比如字符串、数组等)。JVM参数-XX:PretenureSizeThreshold可以设置大对象的大小,如果对象超过设置大小会直接进入老年代,不会进入年轻代,这个参数只在SerialParNew两个收集器下有效。

// 添加运行JVM参数:-XX:+PrintGCDetails -XX:PretenureSizeThreshold=1000000 -XX:+UseSerialGC
public class GCTest {
    public static void main(String[] args) throws InterruptedException {
        byte[] byte1 = new byte[25000*1024];
    }
}

运行结果如下:

Heap
 def new generation   total 39296K, used 4900K [0x0000000081800000, 0x00000000842a0000, 0x00000000abaa0000)
  eden space 34944K,  14% used [0x0000000081800000, 0x0000000081cc92d8, 0x0000000083a20000)
  from space 4352K,   0% used [0x0000000083a20000, 0x0000000083a20000, 0x0000000083e60000)
  to   space 4352K,   0% used [0x0000000083e60000, 0x0000000083e60000, 0x00000000842a0000)
 tenured generation   total 87424K, used 25000K [0x00000000abaa0000, 0x00000000b1000000, 0x0000000100000000)
   the space 87424K,  28% used [0x00000000abaa0000, 0x00000000ad30a010, 0x00000000ad30a200, 0x00000000b1000000)
 Metaspace       used 3484K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 381K, capacity 388K, committed 512K, reserved 1048576K

结论: byte1对象直接进入了老年代

优点: 为了避免为大对象分配内存时的复制操作而降低效率。

4、长期存活的对象将进入老年代

虚拟机采用了分代收集的思想来管理内存,内存回收时能识别哪些对象应放在新生代,哪些对象应放在老年代。为了做到这一点,虚拟机给每个对象一个对象年龄(Age)计数器。如果对象在Eden区出生并经过第一次Minor GC后仍然能够存活,并且能被Survivor区容纳,将被移动到Survivor区,并将对象年龄设为1。对象在Survivor区中每经过一次Minor GC,年龄就增加1,当它的年龄增加到一定程度(默认为15岁),就会被移到老年代中。对象被移到老年代的年龄阈值,可以通过参数-XX:MaxTenuringThreshold来设置。

5、对象动态年龄判断

当前放对象的Survivor区域里(其中一块区域,放对象的那块s区),一批对象的总 大小大于这块Survivor区域内存大小的50%,那么此时大于等于这批对象年龄最 大值的对象,就可以直接进入老年代了,例如Survivor区域里现在有一批对象, 年龄1+年龄2+年龄n的多个年龄对象总和超过了Survivor区域的50%,此时就 会把年龄n以上的对象都放入老年代。这个规则其实是希望那些可能是长期存活 的对象,尽早进入老年代。对象动态年龄判断机制一般是在minor gc之后触发 的。

6、老年代空间分配担保机制

7、Eden与Survivor区默认8:1:1