JVM:GC-内存分配与回收策略

对象优先在Eden区分配

对象优先在eden区分配,当eden区没有足够空间分配内存时,就会发现minor gc.
代码实例:

public class Main {

    static int _1M = 1024*1024;
    //vm 参数
    // -verbose:gc -XX:+PrintGCDetails -Xms20M -Xmx20M -Xmn10M

    public static void main(String[] args) {
        byte[] b1;
        b1 = new byte[2*_1M];
    }
}

JVM参数:
-verbose:gc -XX:+PrintGCDetails : 打印GC日志
-Xms20M -Xmx20M -Xmn10M :堆内存20M,不允许扩展,新生代给10M。
如上在内存中分配2M空间给byte数组。
输出如下:

Heap
 PSYoungGen      total 9216K, used 4055K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
  eden space 8192K, 49% used [0x00000007bf600000,0x00000007bf9f5d40,0x00000007bfe00000)
  from space 1024K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007c0000000)
  to   space 1024K, 0% used [0x00000007bfe00000,0x00000007bfe00000,0x00000007bff00000)
 ParOldGen       total 10240K, used 0K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
  object space 10240K, 0% used [0x00000007bec00000,0x00000007bec00000,0x00000007bf600000)
 Metaspace       used 3159K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 346K, capacity 388K, committed 512K, reserved 1048576K

可以看到只要eden区有使用内存,
49% used ,将近4M的空间,可是我们代码里只分配了两兆,其实还是JVM本身的一些对象吧,不深究。

大对象直接在年老代分配

通过 JVM参数-XX:PretenureSizeThreshold可以设置达到指定大小的大对象直接进入年老代。
为什么要让大对象直接在年老代分配呢?
避免eden区与两个survivor区进行大量的内存复制,也就是避免不必要的minor gc,直接让大对象进入年老代,就不会触发新生代的GC。
注意:这个参数只对Serial和ParNew生效。

public class Main {

    static int _1M = 1024*1024;
    //vm 参数
    //  -XX:+PrintGCDetails -Xms20M -Xmx20M 
 //   -Xmn10M -verbose:gc 
//-XX:PretenureSizeThreshold=3145728  
//-XX:+UseSerialGC -XX:+PrintCommandLineFlags

    public static void main(String[] args) {
        byte[] b1;
        b1 = new byte[6*_1M];
    }
}

设置直接进入老年代的对象阈值为3M:
-XX:PretenureSizeThreshold=3145728

Heap
 def new generation   total 9216K, used 2047K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
  eden space 8192K,  24% used [0x00000007bec00000, 0x00000007bedffdc0, 0x00000007bf400000)
  from space 1024K,   0% used [0x00000007bf400000, 0x00000007bf400000, 0x00000007bf500000)
  to   space 1024K,   0% used [0x00000007bf500000, 0x00000007bf500000, 0x00000007bf600000)
 tenured generation   total 10240K, used 6144K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
   the space 10240K,  60% used [0x00000007bf600000, 0x00000007bfc00010, 0x00000007bfc00200, 0x00000007c0000000)
 Metaspace       used 3300K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 365K, capacity 388K, committed 512K, reserved 1048576K

虽然新生代空间充足,但是这个大对象直接被分配进老年代了。

长期存活对象进入老年代

JVM垃圾收集器采用分代回收的方式来实现GC。
所以内存回收的时候需要知道哪些对象应该在新生代,哪些对象应该放进老年代。
因此JVM为每一个对象维护了一个对象年龄计数器,当对象熬过第一次minor gc 并被survivor接纳时,设置年龄为1,对象在survivor区每熬过一次minor gc 年龄就+1,当对象达到一次年龄,就会进入老年代,这个阈值默认15,可以通过-XX:MaxTenuringThreshold设置。
此外,如果survivor区同龄对象达到survivor的一半,那么大于等于这个年龄的所有对象都会进入老年代,而无需等到阈值年龄

空间分配担保

在进行minor gc前,会先判断老年代最大连续空间是否大于新生代所有对象总空间,如果大于,则正常执行minor gc,此时是安全的。
如果小于,则判断-XX:+HandlePromotionFailure是否设置为允许担保失败。
如果不允许,则直接full gc。
如果允许,就继续判断老年代最大连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,则冒险执行minor gc 。否则就full gc

你可能感兴趣的:(深入理解java虚拟机)