Detailed Explanation of the Interaction Mechanism Between Object References and the finalize() Method in Java

Detailed Explanation of the Interaction Mechanism Between Object References and the finalize() Method in Java

一、Knowledge Point Description

In Java, the finalize() method, as part of the object finalization mechanism, has a complex interaction relationship with Java's four reference types (Strong Reference, Soft Reference, Weak Reference, Phantom Reference). Understanding this interaction mechanism is crucial for correctly handling object lifecycles, preventing memory leaks, and optimizing garbage collection.

二、Core Concept Review

1. Four Reference Types

  • Strong Reference: The most common reference type; as long as a strong reference exists, the object will not be reclaimed.
  • Soft Reference: Reclaimed when memory is insufficient, suitable for cache implementations.
  • Weak Reference: Reclaimed whenever GC occurs.
  • Phantom Reference: The weakest reference, primarily used to track the state of an object's reclamation.

2. finalize() Method

  • A protected method of the Object class.
  • Called by the JVM before an object is garbage collected.
  • finalize() is called at most once per object.

三、Interaction Flow Between References and finalize()

Step 1: Object Reachability Analysis

Object A → Held by a strong reference → Strongly reachable → finalize() is NOT triggered
Object B → Only a weak reference → Weakly reachable → finalize() MAY be triggered

Step 2: First Mark (Determining if finalize() Needs Execution)

public class FinalizeDemo {
    private static FinalizeDemo SAVE_HOOK = null;
    
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("finalize method executed");
        SAVE_HOOK = this;  // Object self-rescue
    }
    
    public static void main(String[] args) throws Exception {
        SAVE_HOOK = new FinalizeDemo();
        
        // First self-rescue attempt
        SAVE_HOOK = null;
        System.gc();  // First GC
        Thread.sleep(500);
        
        if (SAVE_HOOK != null) {
            System.out.println("Object is still alive");
        } else {
            System.out.println("Object is dead");
        }
    }
}

四、Timing of finalize() Invocation for Different Reference Types

1. Strongly Referenced Objects

class StrongRefFinalize {
    @Override
    protected void finalize() throws Throwable {
        System.out.println("Strong reference object's finalize called");
    }
    
    public static void main(String[] args) {
        StrongRefFinalize obj = new StrongRefFinalize();  // Strong reference
        obj = null;  // Remove strong reference
        System.gc();  // finalize() will be called now
    }
}

Timing: Called during GC after the object becomes unreachable.

2. Soft/Weakly Referenced Objects

import java.lang.ref.WeakReference;

class WeakRefFinalize {
    @Override
    protected void finalize() throws Throwable {
        System.out.println("Weak reference object's finalize called");
    }
    
    public static void main(String[] args) {
        WeakRefFinalize obj = new WeakRefFinalize();
        WeakReference<WeakRefFinalize> weakRef = new WeakReference<>(obj);
        
        obj = null;  // Remove strong reference
        System.gc();  // GC will definitely call finalize()
        
        // weakRef.get() may be null
    }
}

Key Point: Soft and weak references do not affect the invocation of finalize(), only the timing of object reclamation.

3. Phantom Referenced Objects

import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;

class PhantomRefFinalize {
    @Override
    protected void finalize() throws Throwable {
        System.out.println("Phantom reference object's finalize called");
    }
    
    public static void main(String[] args) throws Exception {
        PhantomRefFinalize obj = new PhantomRefFinalize();
        ReferenceQueue<PhantomRefFinalize> queue = new ReferenceQueue<>();
        PhantomReference<PhantomRefFinalize> phantomRef = 
            new PhantomReference<>(obj, queue);
        
        obj = null;  // Remove strong reference
        System.gc();
        Thread.sleep(100);
        
        // The characteristic of a phantom reference: get() always returns null
        // Object enters the finalizable state; finalize() has been executed or is pending.
    }
}

Specialty: A phantom reference is enqueued into the ReferenceQueue after the object's finalize() has been called.

五、Detailed Interaction Flow Analysis

Step 1: Object Lifecycle State Transition

Creation → Strongly Reachable → Soft/Weakly Reachable → Unreachable → Finalizable → Finalized → Reclaimed
      ↑          ↓                    ↑
      └──── Object Self-Rescue ─────────────┘

Step 2: finalize() Execution Queue

// Internal JVM processing flow (pseudocode representation)
class Finalizer {
    private static ReferenceQueue<Object> queue = new ReferenceQueue<>();
    private static FinalizerThread thread;  // Finalizer daemon thread
    
    static {
        thread = new FinalizerThread(queue);
        thread.setDaemon(true);
        thread.start();
    }
    
    // When an object needs finalization, the JVM creates a FinalizerReference
    static void register(Object obj) {
        new FinalizerReference(obj, queue);
    }
}

Step 3: Workflow of the Finalizer Daemon Thread

  1. Takes a FinalizerReference from the ReferenceQueue.
  2. Removes it from the Finalizer chain.
  3. Invokes the object's finalize() method.
  4. Clears the FinalizerReference's reference to the object.

六、Key Points to Note

1. Execution Order Issue

class OrderDemo {
    private static OrderDemo obj;
    
    @Override
    protected void finalize() throws Throwable {
        System.out.println("finalize called");
        obj = this;  // Object self-rescue
    }
    
    public static void main(String[] args) throws Exception {
        obj = new OrderDemo();
        
        // First GC
        obj = null;
        System.gc();
        Thread.sleep(1000);  // Wait for finalizer thread
        
        // Second GC
        obj = null;
        System.gc();  // finalize() will NOT be called this time (only once per object)
        Thread.sleep(1000);
    }
}

2. Performance Impact

// Incorrect finalize() implementation
class BadFinalize {
    private byte[] data = new byte[10_000_000];  // 10MB
    
    @Override
    protected void finalize() throws Throwable {
        // Complex operations that impact GC performance
        slowOperation();
        super.finalize();
    }
    
    private void slowOperation() {
        try {
            Thread.sleep(1000);  // Simulate time-consuming operation
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Problem: Slow execution of finalize() can cause objects to pile up in the Finalizer queue, leading to memory leaks.

七、Practical Application Suggestions

1. Correct Approach for Resource Cleanup

// Use try-with-resources instead of finalize()
class ResourceHandler implements AutoCloseable {
    private ExternalResource resource;
    
    public ResourceHandler() {
        resource = acquireResource();
    }
    
    @Override
    public void close() {
        if (resource != null) {
            resource.release();
            resource = null;
        }
    }
    
    // Do not rely on finalize() for resource cleanup
    public static void main(String[] args) {
        try (ResourceHandler handler = new ResourceHandler()) {
            // Use the resource
        }  // close() is called automatically
    }
}

2. Combined Use of References and finalize()

import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;

class ResourceTracker {
    private static final ReferenceQueue<HeavyResource> queue = 
        new ReferenceQueue<>();
    
    static class CleanupThread extends Thread {
        @Override
        public void run() {
            while (true) {
                try {
                    Reference<? extends HeavyResource> ref = queue.remove();
                    // Resource has been reclaimed, perform cleanup.
                    cleanup(ref);
                } catch (InterruptedException e) {
                    break;
                }
            }
        }
    }
}

八、Summary Key Points

  1. finalize() Invocation Timing: Called during GC after an object becomes unreachable, at most once per object.
  2. Impact of Reference Types: Soft and weak references do not affect finalize() invocation, only the reclamation timing.
  3. Special Nature of Phantom References: Must be used with a ReferenceQueue; get() always returns null.
  4. Performance Considerations: Avoid time-consuming operations in finalize().
  5. Modern Alternatives: Prefer try-with-resources and the Cleaner mechanism.

Although this mechanism is complex, understanding it is crucial for mastering Java memory management and avoiding resource leaks.