Calling System.gc()
仅向 JVM 提供提示,但不保证实际的垃圾收集将会发生。
然而,与您的期望相比,更大的问题是垃圾收集与终结不同。
参考 Java 6 文档,System.gc() https://docs.oracle.com/javase/6/docs/api/java/lang/System.html#gc() states:
运行垃圾收集器。
调用 gc 方法表明 Java 虚拟机会努力回收未使用的对象,以使它们当前占用的内存可供快速重用。 ……
相比于System.runFinalization() https://docs.oracle.com/javase/6/docs/api/java/lang/System.html#runFinalization():
运行任何挂起终结的对象的终结方法。
调用此方法表明 Java 虚拟机花费精力来运行已发现已被丢弃但尚未运行其 Finalize 方法的对象的 Finalize 方法。 ……
因此,可能存在“等待最终确定”的情况。 “已发现已被丢弃但其 Finalize 方法尚未运行的对象”。
不幸的是,Java 6 的文档finalize() https://docs.oracle.com/javase/6/docs/api/java/lang/Object.html#finalize()从误导性的句子开始:
当垃圾收集确定不再有对该对象的引用时,由该对象的垃圾收集器调用。
而垃圾收集和终结是两个不同的事情,因此,finalize()
方法是not由垃圾收集器调用。但请注意,后续文档说:
Java 编程语言不保证哪个线程将调用finalize
任何给定对象的方法。
因此,当你说“输出结果的顺序有点令人难以置信”时,请记住我们在这里讨论的是多线程,因此在没有额外同步的情况下,顺序is在你的控制之外。
Java 语言规范 https://docs.oracle.com/javase/specs/jls/se6/html/execution.html#12.6甚至说:
Java 编程语言没有指定多久将调用终结器,只是说它将在重用对象的存储之前发生。
以及后来
Java 编程语言对 Finalize 方法调用没有强加任何顺序。终结器可以按任何顺序调用,甚至可以同时调用。
实际上,垃圾收集器只会将需要终结的对象放入队列,而一个或多个终结器线程轮询队列并执行finalize()
方法。当所有终结器线程都忙于执行特定的finalize()
方法时,需要终结的对象队列可能会增长任意长。
请注意,现代 JVM 对那些没有专用的类进行了优化finalize()
方法,即继承该方法Object
或者只有一个空方法。这些类的实例(所有对象中的大多数)会跳过此终结步骤,并且它们的空间会立即被回收。
所以如果你添加了一个finalize()
方法只是为了找出对象何时被垃圾回收,这就是它的存在finalize()
减慢内存回收过程的方法。
所以最好参考 JDK 11 版本finalize() https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Object.html#finalize():
Deprecated.
最终确定机制本质上是有问题的。最终确定可能会导致性能问题、死锁和挂起。终结器中的错误可能导致资源泄漏;如果不再需要,则无法取消最终确定;并且不同对象的 Finalize 方法的调用之间没有指定顺序。此外,我们无法保证最终确定的时间。仅在无限期延迟(如果有)之后才可以对可终结对象调用 Finalize 方法。实例持有非堆资源的类应该提供一种方法来显式释放这些资源,并且如果合适,它们还应该实现 AutoCloseable。 Cleaner 和 PhantomReference 提供了更灵活、更有效的方法来在对象变得无法访问时释放资源。
因此,当您的对象不包含非内存资源,因此实际上不需要终结时,您可以使用
class Test
{
int x = 100;
int y = 115;
}
class DelObj
{
public static void main(String[] arg)
{
Test t1 = new Test();
System.out.println("Values are "+t1.x+", "+t1.y+"\nObject refered by t1 is at location: "+t1);
WeakReference<Test> ref = new WeakReference<Test>(t1);
t1 = null; // dereferencing
System.gc(); // explicitly calling
if(ref.get() == null) System.out.println("Object deallocation is completed");
else System.out.println("Not collected");
Test t2= new Test();
System.out.println("Values are "+t2.x+", "+t2.y+"\nObject refered by t2 is at location: "+t2);
}
}
The System.gc()
call still 只是一个提示,但在大多数实际情况下,您会发现您的对象随后被收集。请注意,为对象打印的哈希代码,例如Test@67f1fba0
与内存位置无关;这是一个顽强的神话。对象内存地址背后的模式通常不适合hashing,此外,大多数现代 JVM 可以在对象的生命周期内将对象移动到不同的内存位置,而身份哈希码保证保持不变。