前言
System.gc()无法保证虚拟机每次都进行垃圾回收,所以finalize()不是一定会触发的,要解决该问题有两种实现方式
- 强制调用
System.runFinalization()让已经失去引用的对象的finalize方法;- 调用
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