Java的垃圾收集器对内存的管理起到了重要的作用,其中内存被分为了新生代和老年代。新生代是用来存放刚创建的对象的,而老年代则用来存放生命周期较长的对象。新生代中的对象在经过一定次数的垃圾回收之后,如果还存活着,就会被移动到老年代中。下面将介绍几种方式。

方式一:对象年龄达到阈值

在Java的垃圾回收中,每个对象创建后都有一个计数器用于记录对象的年龄,当对象经过一次Minor GC(年轻代的垃圾回收)后还存活着,它的年龄就会加1。当对象的年龄达到一个阈值时,会被晋升到老年代中。

public class ObjectPromotion {
    private static final int _1MB = 1024 * 1024;
    
    public static void main(String[] args) {
        byte[] allocation1, allocation2, allocation3;
        allocation1 = new byte[_1MB / 4];
        // allocation1+allocation2大于Survivor空间的一半
        allocation2 = new byte[_1MB / 4];
        allocation3 = new byte[4 * _1MB];
        allocation3 = null;
        allocation3 = new byte[4 * _1MB];
    }
}

上述示例中,分配了三个对象,分别为allocation1、allocation2和allocation3。其中,allocation1和allocation2的大小为新生代的1/4。这意味着它们会被分配到Eden空间,而allocation3的大小为老年代的一半。在分配allocation3之前,已经进行了一次Minor GC。在第二次分配allocation3时,Eden空间已经无法容纳,所以需要进行Minor GC。此时,由于allocation1和allocation2还存活着,它们的年龄会被增加,而allocation3的年龄为1,因为这是第一次通过Minor GC。

方式二:Survivor空间不足

在新生代中,有两个Survivor空间,它们的作用是将存活下来的对象移动到另一个Survivor空间或者老年代中。当一个Survivor空间被填满后,如果没有足够的空间将存活下来的对象移动到另一个Survivor空间,就会选择将年龄大的对象晋升到老年代中。

public class SurvivorSpace {
    private static final int _1MB = 1024 * 1024;
    
    public static void main(String[] args) {
        byte[] allocation1, allocation2, allocation3, allocation4;
        allocation1 = new byte[_1MB / 4];
        allocation2 = new byte[_1MB / 4];
        allocation3 = new byte[4 * _1MB];
        allocation4 = new byte[4 * _1MB];
        allocation4 = null;
        allocation4 = new byte[4 * _1MB];
    }
}

在上述示例中,分配了四个对象,其中allocation1和allocation2的大小为新生代的1/4,allocation3和allocation4的大小为老年代的一半。在执行到第二次分配allocation4时,Eden空间已经无法容纳,所以需要进行一次Minor GC。由于allocation1和allocation2还存活着,它们会被移动到其中一个Survivor空间。而allocation3和allocation4的大小超过Survivor空间的一半,因此会晋升到老年代中。

方式三:长期存活的对象

在Java的垃圾回收中,如果一个对象经过了多次Minor GC仍然存活着,那么它有可能会被晋升到老年代中。这是因为经过多次Minor GC后,存活下来的对象有很大的可能性是长期存活的对象,将它们移动到老年代中可以提高垃圾回收的效率。

public class LongLiveObject {
    private static final int _1MB = 1024 * 1024;
    
    public static void main(String[] args) {
        byte[] allocation1, allocation2, allocation3;
        allocation1 = new byte[_1MB / 4];
        allocation2 = new byte[_1MB / 4];
        allocation3 = new byte[4 * _1MB];
        allocation3 = null;
        allocation3 = new byte[4 * _1MB];
        System.gc();
    }
}

在上述示例中,分配了三个对象,其中allocation1和allocation2的大小为新生代的1/4,allocation3的大小为老年代的一半。在执行到第二次分配allocation3后,Eden空间已经无法容纳,因此需要进行一次Minor GC。由于allocation1和allocation2还存活着,它们会被移动到Survivor空间。然后,执行了System.gc()方法,触发一次Full GC。由于allocation1和allocation2经过了多次Minor GC仍然存活着,它们会被晋升到老年代中。