JVM-关于System.gc那些事(6)

303 阅读4分钟

前言

System.gc()无法保证虚拟机每次都进行垃圾回收,所以finalize()不是一定会触发的,要解决该问题有两种实现方式

  1. 强制调用System.runFinalization()让已经失去引用的对象的finalize方法;
  2. 调用Thread.sleep()让主线程给优先级较低的Finalizer线程让步;

示例

No1

public class SystemGCTest {

    public static void main(String[] args) throws InterruptedException {

        new SystemGCTest();

        System.gc();

        // 由于 Finalizer 线程优先级较低,所以让 main 线程暂停 2000s,让 jvm 虚拟机有时间调用finalize()进行对象复活
//        Thread.sleep(1000);

        // 强制调用已经失去引用的对象的finalize方法
        System.runFinalization();
    }

    @Override
    protected void finalize() throws Throwable {
        System.out.println("SystemGCTest.finalize");
    }
}

输出结果:SystemGCTest.finalize

在上述代码种如果不强制调用System.runFinalization()那么最终的结果是不一定会输出SystemGCTest.finalize的,因为JVM的官方文档有明确的免责声明给开发者,虽然可以手动调用System.gc(),但是系统不保证一定会执行。

No2

/**
 * 虚拟机参数:-Xmx15m -Xms15m -XX:+PrintGCDetails
 */
public class LocalVarTest {

    public static void main(String[] args) {
        LocalVarTest test = new LocalVarTest();
        test.t2();
    }


    /**
     * [GC (System.gc())
     * ---[新生代:      使用容量 -> GC后容量(分配容量)]    GC前Heap区容量 -> GC后Heap区容量(Heap区总容量), ]
     * ---[PSYoungGen: 1795K  -> 504K   (4608K) ]     12035K       -> 10908K       (15872K),     ]
     * [Full GC (System.gc())
     * ---[PSYoungGen: 504K->0K(4608K)]
     * ---[ParOldGen: 10404K->10880K(11264K)]         10908K->10880K(15872K)                     ]
     * 新生代发生了一次YoungGC,将大对象放入老年代,Heap发生了一次Full GC,但是没有清除老年代的对象
     */
    public void t1() {
        byte[] bytes = new byte[10 * 1024 * 1024];

        System.gc();
    }

    /**
     * [GC (System.gc())
     * ---[PSYoungGen: 1795K->488K(4608K)] 12035K->10944K(15872K)]
     * [Full GC (System.gc())
     * ---[PSYoungGen: 488K->0K(4608K)] [ParOldGen: 10456K->10880K(11264K)] 10944K->10880K(15872K)]
     * [GC (System.gc())
     * ---[PSYoungGen: 0K->0K(4608K)] 10880K->10880K(15872K)]
     * [Full GC (System.gc())
     * ---[PSYoungGen: 0K->0K(4608K)] [ParOldGen: 10880K->640K(11264K)] 10880K->640K(15872K)]
     * <p>
     * 发生了两次 YoungGC 和 两次 Full GC,在第二次进行Full GC 时,将对老年代进行垃圾回收
     */
    public void t2() {
        t1();
        System.gc();
    }

    /**
     * [GC (System.gc())
     * ---[PSYoungGen: 1795K->488K(4608K)] 12035K->10960K(15872K)]
     * [Full GC (System.gc())
     * ---[PSYoungGen: 488K->0K(4608K)] [ParOldGen: 10472K->10881K(11264K)] 10960K->10881K(15872K)]
     * <p>
     * 虽然 bytes 的生命周期在 {} 内,但是通过打印heap日志来看,并没有对其进行垃圾回收,只是放到了老年代中.
     * 通过 jclasslib 可以观察到局部变量表的slot是2
     */
    public void t3() {
        {
            byte[] bytes = new byte[10 * 1024 * 1024];
        }
        System.gc();
    }

    /**
     * [GC (System.gc())
     * ---[PSYoungGen: 1705K->488K(4608K)] 11945K->10928K(15872K), 0.0008247 secs]
     * [Full GC (System.gc())
     * ---[PSYoungGen: 488K->0K(4608K)] [ParOldGen: 10440K->608K(11264K)] 10928K->608K(15872K)]
     *
     * 通过日志来看发现 bytes 对象在进行Full GC时被回收了.
     * 和 t3()相比,我们在该方法内又声明了一个变量为 i 来占用 slot 位置,这时局部变量表的最大数量也是 2
     */
    public void t4() {
        {
            byte[] bytes = new byte[10 * 1024 * 1024];
        }
        int i = 0;
        System.gc();
    }
}

输出结果:
t1 新生代发生了一次YoungGC,将大对象放入老年代,Heap发生了一次Full GC,但是没有清除老年代的对象
t2 发生了两次 YoungGC 和 两次 Full GC,在第二次进行Full GC 时,将对老年代进行垃圾回收
t3 虽然 bytes 的生命周期在 {} 内,但是通过打印heap日志来看,并没有对其进行垃圾回收,只是放到了老年代中.通过 jclasslib 可以观察到局部变量表的slot是2,所以并不会对其回收
t4 通过日志来看发现 bytes 对象在进行Full GC时被回收了.和 t3()相比,我们在该方法内又声明了一个变量为 i 来占用 slot 位置,这时局部变量表的最大数量也是 2