为什么 finalize() 只在创建新对象后执行,而不是在调用 gc() 后执行?

Why does finalize() execute only after new object is created, but not after gc() is invoked?

调用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);

    } 
}

得到新对象创建后finalize()的执行结果,引用t2:

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

调用 System.gc() 仅向 JVM 提供提示,但不保证会发生实际的垃圾收集。

然而,与您的期望相比,更大的问题是垃圾收集与终结不同。

参考 Java6 文档,System.gc() 指出:

Runs the garbage collector.

Calling the gc method suggests that the Java Virtual Machine expend effort toward recycling unused objects in order to make the memory they currently occupy available for quick reuse. …

System.runFinalization()比较:

Runs the finalization methods of any objects pending finalization.

Calling this method suggests that the Java Virtual Machine expend effort toward running the finalize methods of objects that have been found to be discarded but whose finalize methods have not yet been run. …

所以可以有“待定案”,resp。 “已发现被丢弃但其finalize方法尚未运行的对象”。

不幸的是,Java6 的 finalize() 文档以误导性句子开头:

Called by the garbage collector on an object when garbage collection determines that there are no more references to the object.

而垃圾收集和终结是两个不同的事物,因此,finalize() 方法是 而不是 由垃圾收集器调用的。但请注意,随后的文档说:

The Java programming language does not guarantee which thread will invoke the finalize method for any given object.

所以当你说“输出结果的顺序有点令人信服”时,回想一下我们这里讨论的是多线程,所以在没有额外同步的情况下,顺序 在你的控制之外。

The Java Language Specification甚至说:

The Java programming language does not specify how soon a finalizer will be invoked, except to say that it will happen before the storage for the object is reused.

以后

The Java programming language imposes no ordering on finalize method calls. Finalizers may be called in any order, or even concurrently.

实际上,垃圾收集器只会将需要终结的对象入队,而一个或多个终结器线程会轮询队列并执行 finalize() 方法。当所有终结器线程都忙于执行特定的 finalize() 方法时,需要终结的对象队列可能会增长任意长。

请注意,现代 JVM 包含针对那些 类 没有专用 finalize() 方法的优化,即从 Object 继承方法或只有一个空方法。这些 类 的实例(所有对象中的大多数)跳过此最终确定步骤,它们的 space 会立即被回收。

因此,如果您添加一个 finalize() 方法只是为了查明对象何时被垃圾收集,那么正是该 finalize() 方法的存在减慢了内存回收过程。

所以最好参考 finalize() 的 JDK11 版本:

Deprecated.

The finalization mechanism is inherently problematic. Finalization can lead to performance issues, deadlocks, and hangs. Errors in finalizers can lead to resource leaks; there is no way to cancel finalization if it is no longer necessary; and no ordering is specified among calls to finalize methods of different objects. Furthermore, there are no guarantees regarding the timing of finalization. The finalize method might be called on a finalizable object only after an indefinite delay, if at all. Classes whose instances hold non-heap resources should provide a method to enable explicit release of those resources, and they should also implement AutoCloseable if appropriate. The Cleaner and PhantomReference provide more flexible and efficient ways to release resources when an object becomes unreachable.

所以当你的对象不包含非内存资源时,实际上不需要终结,你可以使用

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 可以在其生命周期内将对象移动到不同的内存位置,而身份哈希码保证保持不变。