使用 java.lang.ref.Cleaner 作为 Object.finalize 的替代品

3 java garbage-collection

我需要清理 JNI 调用分配的资源。通过重写方法很容易做到这一点Object.finalize()。由于从 Java 9 开始不推荐使用此方法,因此我尝试使用新java.lang.ref.Cleaner类来实现相同的目标。

ToBeCleaned.cleanUp以下是在实例被垃圾收集之前调用方法的代码:

import java.lang.ref.Cleaner;
import java.lang.ref.WeakReference;

public class ToBeCleaned {

    private static Cleaner cleaner = Cleaner.create();

    public ToBeCleaned() {
        cleaner.register(this, new CleanRunnable(this));
    }

    void cleanUp () {
        // do cleanup
    }


    static class CleanRunnable implements Runnable {
        // It has to be weak reference, otherwise ToBeCleaned instance
        // would never be eligible for GC
        private WeakReference<ToBeCleaned> toBeCleanedWeakReference;

        CleanRunnable(ToBeCleaned  toBeCleaned) {
            this.toBeCleanedWeakReference = new WeakReference<>(toBeCleaned);
        }

        @Override
        public void run() {
            toBeCleanedWeakReference.get().cleanUp();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我的问题:这是正确的方法吗?

Sla*_*law 5

Your approach has a flaw. The "cleaning action" must not depend on having access to the instance registered with the Cleaner.

In short, the call to toBeCleanedWeakReference.get() in your code will return null since the ToBeCleaned instance will have been, at least from our perspective, garbage collected by that point.

The correct approach is to somehow reference the resource that needs to be cleaned up without "going through" the ToBeCleaned instance. Typically this means either:

  1. Making the cleaning action and the resource the same object (distinct from the object registered with the Cleaner). The documentation of Cleaner shows an example of this approach.

  2. Passing a reference to the resource, but not the object registered with the Cleaner, to the cleaning action when instantiating it. Here's an example:

    public class ToBeCleaned implements AutoCloseable {
    
      // documentation suggests you should preferably have one
      // Cleaner instance per library
      private static final Cleaner CLEANER = ...;
    
      private final Cleaner.Cleanable cleanable;
      private final SomeResource resource;
    
      public ToBeCleaned() {
        resource = ...;
        cleanable = CLEANER.register(this, new CleaningAction(resource));
      }
    
      @Override
      public void close() {
        cleanable.clean();
      }
    
      private static class CleaningAction implements Runnable {
    
        private final SomeResource resource;
    
        CleaningAction(SomeResource resource) {
          this.resource = resource;
        }
    
        @Override
        public void run() {
          // clean up 'resource'
        }
      }
    }
    
    Run Code Online (Sandbox Code Playgroud)

Both examples implement AutoCloseable. That gives users of your API the ability to release the resources on-demand rather than waiting for the garbage collector to kick in (which makes the Cleaner more of a "back up").

  • 对于软引用,是的,至少一个软引用的存在表明垃圾收集器应该不那么积极地回收它。对于幻像引用,只有在保证没有终结器可以复活对象时才应进行清除和入队。此时,所有其他引用也应该被清除,但更进一步,由于问题是关于“Cleaner 作为 Object.finalize 的替代品”,所以这种差异应该不重要。当任何对象都无法使用“重要的终结方法”访问某个对象时,弱引用和幻像引用的行为相同。 (2认同)
  • 我现在明白为什么我的方法是完全错误的:“只有在关联对象变得幻像可达之后才会调用清理操作,因此实现清理操作的对象不保存对该对象的引用非常重要。” (java.lang.ref.Cleaner Javadoc)。我在阅读 Javadoc 时错过了这一点。 (2认同)