使用通用的PhantomReference类删除本地对等体

8
如Google I/O '17演讲“如何在Android中管理本机C++内存”中的Hans Boehm建议,我使用PhantomReference类确保本机对等体被正确删除。
18分57秒的链接视频中,他展示了一个对象将自身注册到其类型的PhantomReference类的示例实现。他在19分49秒展示了这个PhantomReference类。因此,我复制了他的方法来处理我的示例对象。请参见下文。 虽然这种方法很好用,但不可扩展。我需要创建相当数量的对象,但我还没有找到一种方法来创建基类(无论是针对我的对象还是PhantomReference基类),该基类将处理本机删除。
我该如何创建一个通用的基础PhantomReference类,使其能够调用所提供对象的本地静态方法?
我尝试转换通用的PhantomReference,但是本地静态删除方法会阻碍实现。
我的WorkViewModel
import android.databinding.*;

public class WorkViewModel extends BaseObservable
{
  private long _nativeHandle;

  public WorkViewModel(Database database, int workId)
  {
    _nativeHandle = create(database.getNativeHandle(), workId);
    WorkViewModelPhantomReference.register(this, _nativeHandle);
  }

  private static native long create(long databaseHandle, int workId);
  static native void delete(long nativeHandle);

  @Bindable
  public native int getWorkId();
  public native void setWorkId(int workId);
}

我的WorkViewModelPhantomReference

import java.lang.ref.*;
import java.util.*;

public class WorkViewModelPhantomReference extends PhantomReference<WorkViewModel>
{
  private static Set<WorkViewModelPhantomReference> phantomReferences = new HashSet<WorkViewModelPhantomReference>();
  private static ReferenceQueue<WorkViewModel> garbageCollectedObjectsQueue = new ReferenceQueue<WorkViewModel>();
  private long _nativeHandle;

  private WorkViewModelPhantomReference(WorkViewModel workViewModel, long nativeHandle)
  {
    super(workViewModel, garbageCollectedObjectsQueue);
    _nativeHandle = nativeHandle;
  }

  public static void register(WorkViewModel workViewModel, long nativeHandle)
  {
    phantomReferences.add(new WorkViewModelPhantomReference(workViewModel, nativeHandle));
  }

  public static void deleteOrphanedNativePeerObjects()
  {
    WorkViewModelPhantomReference reference;

    while((reference = (WorkViewModelPhantomReference)garbageCollectedObjectsQueue.poll()) != null)
    {
      WorkViewModel.delete(reference._nativeHandle);
      phantomReferences.remove(reference);
    }
  }
}

1
我立即相信这种方法不可扩展。但是我不理解你的第二个问题,即“没有找到一种方法来创建一个基类...它可以处理任何对象并正确处理本地删除”。你已经有了由两个类组成的工作解决方案。那个假设的基类应该解决什么问题,以及如何解决? - Holger
@Holger,感谢您的回复。请解释一下比例问题?这就是我想用一个假设的基类来解决的问题。我有很多这样的对象,对于每个对象,我都必须创建第二个虚拟类。使用基类,我希望能够解决这个问题,而不需要创建额外的类或使其变得简单,以至于我只需要定义类型即可。 - Bruno Bieri
1
等等,你每创建一个对象就要创建一个新的幻影类吗?或者你所说的“每个”是什么意思? - Holger
这正是我目前正在做的事情(在我上面的问题的第三段)。所以这就是为什么我问这个问题是否有更好的方法来做到这一点。 - Bruno Bieri
1
看起来你混淆了对象和类。我不明白为什么你要为每个对象创建一个新的。我猜想,你需要为每个具有不同delete方法的不同创建一个新的,对吗?并且你需要为每个需要清理的对象创建另一个新的虚拟对象。当我第一次读到“不可扩展”时,我以为你在谈论这种设计的性能差,这部分与你必须创建的对象有关,然而,似乎你主要是在谈论代码复杂性,这与类有关。后者可能是可以解决的。 - Holger
@Holger 是的,我混淆了。你有什么建议如何解决“后者”? - Bruno Bieri
1个回答

8

您可以查看Java 9的Cleaner API,它解决了类似的任务,围绕PhantomReference进行清理,并实现类似的东西,使其适应您的需求。由于您不需要支持多个清理器,因此可以使用静态注册方法。建议保留引用的抽象,即Cleanable接口,以确保不会调用任何继承的引用方法,特别是在clear()clean()易混淆的情况下:

public class Cleaner {
    public interface Cleanable {
        void clean();
    }
    public static Cleanable register(Object o, Runnable r) {
        CleanerReference c = new CleanerReference(
                Objects.requireNonNull(o), Objects.requireNonNull(r));
        phantomReferences.add(c);
        return c;
    }
    private static final Set<CleanerReference> phantomReferences
                                             = ConcurrentHashMap.newKeySet();
    private static final ReferenceQueue<Object> garbageCollectedObjectsQueue
                                              = new ReferenceQueue<>();

    static final class CleanerReference extends PhantomReference<Object>
                                        implements Cleanable {
        private final Runnable cleaningAction;

        CleanerReference(Object referent, Runnable action) {
            super(referent, garbageCollectedObjectsQueue);
            cleaningAction = action;
        }
        public void clean() {
            if(phantomReferences.remove(this)) {
                super.clear();
                cleaningAction.run();
            }
        }
    }
    public static void deleteOrphanedNativePeerObjects() {
        CleanerReference reference;
        while((reference=(CleanerReference)garbageCollectedObjectsQueue.poll()) != null) {
            reference.clean();
        }
    }
}

此代码使用了Java 8的特性。如果无法使用ConcurrentHashMap.newKeySet(),可以改用Collections.newSetFromMap(new ConcurrentHashMap<CleanerReference,Boolean>())

保留deleteOrphanedNativePeerObjects()方法来显式触发清理操作,但它是线程安全的,因此可以创建一个守护进程后台线程来在将项加入队列时立即清理,就像原始方法中一样。

将操作表示为Runnable允许将其用于任意资源,并且获取Cleanable支持显式清理而不依赖垃圾收集器,同时仍然具有那些未关闭对象的安全保障。

public class WorkViewModel extends BaseObservable implements AutoCloseable
{
    private long _nativeHandle;
    Cleaner.Cleanable cleanable;

    public WorkViewModel(Database database, int workId)
    {
      _nativeHandle = create(database.getNativeHandle(), workId);
      cleanable = createCleanable(this, _nativeHandle);
    }
    private static Cleaner.Cleanable createCleanable(Object o, long _nativeHandle) {
        return Cleaner.register(o, () -> delete(_nativeHandle));
    }

    @Override
    public void close() {
        cleanable.clean();
    }

    private static native long create(long databaseHandle, int workId);
    static native void delete(long nativeHandle);

    @Bindable
    public native int getWorkId();
    public native void setWorkId(int workId);

}

通过实现AutoCloseable接口,可以将其与try-with-resources结构一起使用,但如果没有简单的代码块作用域,则也可以手动调用close()。手动关闭它的优点不仅在于底层资源更早地关闭,而且虚幻对象会从Set中移除并永远不会排队,使整个生命周期更加高效,特别是当您在短时间内创建和使用大量对象时。但是,如果没有调用close(),则清理程序最终会被垃圾回收器排队。
对于支持手动关闭的类,保留标志将非常有用,以检测并拒绝在关闭后尝试使用它的操作。
如果您的目标不支持lambda表达式,则可以通过内部类实现Runnable;它仍然比创建另一个幽灵引用的子类简单。必须注意不要捕获this实例,因此在上面的示例中,创建已移动到static方法中。在没有this的情况下,不可能意外捕获它。该方法还将引用声明为Object,以强制使用参数值而不是实例字段。

它运行得非常好。非常感谢。我已经用自己的类替换了Runnable,因为Runnable经常涉及线程使用,我不想引入任何可能的混淆。 - Bruno Bieri
我不得不使用Collections.newSetFromMap(new ConcurrentHashMap<CleanerReference,Boolean>())调用,因为Android应用的目标版本是API Level 15。我遇到了以下问题https://dev59.com/Ll8e5IYBdhLWcg3wfaUy#25705596 - Bruno Bieri
1
好的,Collections.newSetFromMap(…)是我在Java 8之前的环境下也建议的方法。请注意newKeySet()keySet()之间的区别:前者是一个特殊的ConcurrentHashMap工厂方法,仅存在于Java 8或更新版本中,后者是一个通用的Map方法,返回一个映射的键集视图,并且不支持add操作,因此,无论如何都不合适。 - Holger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接