使用 `java.lang.ref.Cleaner` 作为替代 `Object.finalize` 的方法。

huangapple go评论80阅读模式
英文:

Usage of java.lang.ref.Cleaner as alternative to Object.finalize

问题

以下是翻译好的内容:

我需要清理通过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() {
        // 进行清理操作
    }

    static class CleanRunnable implements Runnable {
        // 必须使用弱引用,否则 ToBeCleaned 实例将永远不会被标记为可回收
        private WeakReference<ToBeCleaned> toBeCleanedWeakReference;

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

        @Override
        public void run() {
            toBeCleanedWeakReference.get().cleanUp();
        }
    }
}

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

英文:

I need to clean up resources allocated by a JNI call. It's easy to do it by overriding Object.finalize() method. Since this method is deprecated starting with Java 9, I'm trying to achieve the same thing using the new java.lang.ref.Cleaner class.

Here is the code for calling ToBeCleaned.cleanUp method before the instance is garbage collected:

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&lt;ToBeCleaned&gt; toBeCleanedWeakReference;

        CleanRunnable(ToBeCleaned  toBeCleaned) {
            this.toBeCleanedWeakReference = new WeakReference&lt;&gt;(toBeCleaned);
        }

        @Override
        public void run() {
            toBeCleanedWeakReference.get().cleanUp();
        }
    }
}

My question: Is this the right approach?

答案1

得分: 5

你的方法存在一个缺陷。"清理操作"不能依赖于对注册在Cleaner中的实例的访问。

简而言之,你代码中的toBeCleanedWeakReference.get()调用会返回null,因为从我们的角度来看,ToBeCleaned实例在那时可能已经被垃圾回收。

正确的方法是在不"通过" ToBeCleaned 实例的情况下引用需要清理的资源。通常有以下两种方式:

  1. 使清理操作和资源成为同一个对象(与注册在Cleaner中的对象不同)。Cleaner文档展示了这种方法的示例。

  2. 在实例化清理操作时,向其传递对资源的引用,而不是注册在Cleaner中的对象。以下是一个示例:

    public class ToBeCleaned implements AutoCloseable {
    
      // 文档建议您最好为每个库使用一个 Cleaner 实例
      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() {
          // 清理 'resource'
        }
      }
    }
    

这两个示例都实现了 AutoCloseable 接口。这使得您的 API 用户能够根据需要释放资源,而不必等待垃圾收集器启动(这使得Cleaner更像是一个"备份")。

英文:

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 &#39;resource&#39;
        }
      }
    }
    

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").

huangapple
  • 本文由 发表于 2020年10月21日 15:51:16
  • 转载请务必保留本文链接:https://go.coder-hub.com/64459003.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定