调用gc()时,finalize()是否应立即执行?输出结果的顺序有点令人信服。
class Test
{
int x = 100;
int y = 115;
protected void finalize()
{System.out.println("Resource Deallocation is completed");}
}
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);
t1 = null; // dereferencing
System.gc(); // explicitly calling
Test t2= new Test();
System.out.println("Values are "+t2.x+", "+t2.y+"\nObject refered by t2 is at location: "+t2);
}
}
在创建一个新对象(由t2引用)后获得finalize()的执行结果:
D:\JavaEx>java DelObj
Values are 100, 115
Object refered by t1 is at location: Test@6bbc4459
Values are 100, 115
Object refered by t2 is at location: Test@2a9931f5
Resource Deallocation is completed
答案 0 :(得分:1)
调用System.gc()
仅向JVM提供提示,但不能保证会发生实际的垃圾回收。
但是,您期望更大的问题是垃圾回收与最终化不同。
请参阅Java 6文档,System.gc()
指出:
运行垃圾收集器。
调用gc方法表明Java虚拟机在回收未使用的对象上花费了很多精力,以使它们当前占用的内存可用于快速重用。 …
运行任何要结束的对象的结束方法。
调用此方法表明,Java虚拟机将花更多的精力来运行已发现被丢弃但尚未运行finalize方法的对象的finalize方法。 …
因此,可能会有“待定”。 “已被发现丢弃但尚未运行终结方法的对象。”
不幸的是,Java 6的finalize()
文档以令人误解的句子开头:
当垃圾回收确定不再有对该对象的引用时,由垃圾回收器在对象上调用。
垃圾收集和完成是两件事,因此,finalize()
方法不是 ,由垃圾收集器调用。但是请注意,随后的文档说:
Java编程语言不能保证哪个线程将为任何给定对象调用
finalize
方法。
因此,当您说“输出结果的顺序有点令人信服”时,请记住我们在这里谈论的是多线程,因此在没有额外同步的情况下,顺序 在您的外部控制。
The Java Language Specification甚至说:
Java编程语言没有指定终结器被调用的时间,只是说它将在重用对象的存储之前发生。
以及以后的
Java编程语言对finalize方法调用不加任何排序。终结器可以按任何顺序调用,甚至可以同时调用。
在实践中,垃圾收集器只会使需要终结的对象排队,而一个或多个终结器线程将轮询队列并执行finalize()
方法。当所有终结器线程都忙于执行特定的finalize()
方法时,需要终结器的对象队列可能会任意长。
请注意,现代JVM对那些不具有专用finalize()
方法的类进行了优化,即从Object
继承该方法或仅具有一个空方法。这些类的实例(占所有对象的大多数)跳过此完成步骤,并立即回收其空间。
因此,如果您添加了finalize()
方法只是为了发现对象何时被垃圾回收,那正是finalize()
方法的存在,从而减慢了内存回收的过程。
因此,最好参考finalize()
的JDK 11版本:
已弃用。终结机制本质上是有问题的。终结处理可能会导致性能问题,死锁和挂起。终结器中的错误可能导致资源泄漏;如果不再需要取消终结,则无法取消;并且在用于终结不同对象的方法的调用之间未指定任何顺序。此外,无法保证最终确定的时间。只有在不确定的延迟之后(如果有的话),才可以在可终结对象上调用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);
}
}
System.gc()
调用仍然仅是一个提示,但是在大多数实际情况下,您将发现随后收集了对象。请注意,为对象打印的哈希码,例如Test@67f1fba0
与存储位置无关。那是一个顽强的神话。对象内存地址后面的模式通常不适合散列,此外,大多数现代JVM可以在其生命周期内将对象移至不同的存储位置,而身份散列代码则保证保持不变。