【问题标题】:Delete native peer with general PhantomReference class删除具有通用 PhantomReference 类的本地对等点
【发布时间】:2018-02-19 00:51:20
【问题描述】:

正如 Hans Boehm 在 Google I/O '17 演讲“How to Manage Native C++ Memory in Android”中建议我使用 PhantomReferenceclass 来确保正确删除本地对等点。

18 min 57 sec 的链接视频中,他展示了一个将自身注册到PhantomReference 类的对象的示例实现。这个PhantomReference 课程,他随后在19 min 49 sec 显示。所以我为我的示例对象复制了他的方法。见下文。

虽然这种方法效果很好,但它无法扩展。我需要创建相当多的对象,但我还没有找到创建基类的方法(无论是为我的对象还是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);
    }
  }
}

【问题讨论】:

  • 我立即相信这种方法无法扩展。但我不明白您的第二个问题,即您“还没有找到创建基类的方法……它可以接受任何对象并正确处理本机删除”。您有一个由两个类组成的有效解决方案。假设的基类应该解决什么问题以及如何解决?
  • @Holger 感谢您的回复。请解释一下规模问题?这就是我试图用一个假设的基类来解决的问题。我有很多这样的对象,我必须为每个对象创建第二个幻像类。有了一个基类,我想解决我不需要创建一个额外的类或者让它变得简单到我只需要定义类型。
  • 等等——你为每个你创建的对象创建一个新的幻像类?或者你所说的“每一个”是什么意思?
  • 这正是我目前所做的(我上面问题的第三段)。所以这就是为什么我问这个问题是否有更好的方法来做到这一点。
  • 看起来你混淆了对象和类。我不明白为什么要为每个 object 创建一个新的 class。我想,对于每个具有不同 delete 方法的不同 class,您需要一个新的 class,对吗?对于每个需要清理的 object,您需要另一个新的幻像 object。当我第一次阅读“不扩展”时,我想,您是在谈论这种设计的性能不佳,这部分与您必须创建的对象有关,但是,您似乎主要是在谈论代码复杂性,这与类有关。后者可能是可以解决的

标签: java android memory-management phantom-reference


【解决方案1】:

您可以查看 Java 9 的 Cleaner API,它解决了类似的任务,围绕 PhantomReference 构建了清理,并实现了类似的东西,并根据您的需要进行了调整。由于您不需要支持多个清洁器,您可以使用static 注册方法。我建议保留引用的抽象,即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&lt;CleanerReference,Boolean&gt;())

它保留了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 经常提到线程的使用,我不想引入任何可能的混淆。
  • 我不得不使用Collections.newSetFromMap(new ConcurrentHashMap&lt;CleanerReference,Boolean&gt;())调用,因为Android App的目标版本是API Level 15。我遇到了以下问题stackoverflow.com/a/25705596/1306012
  • 好吧,Collections.newSetFromMap(…) 也是我在对 Java 8 之前的环境的回答中提出的建议。注意newKeySet()keySet()之间的区别:前者是一个特殊的ConcurrentHashMap工厂方法,它只存在于Java 8或更高版本中,后者是一个通用的Map方法,它返回一个集合视图到一个map 并且不支持add 操作,因此无论如何都不合适。
猜你喜欢
  • 1970-01-01
  • 2017-05-14
  • 1970-01-01
  • 2011-09-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多