Удалить нативный узел с общим классом PhantomReference

Как говорит Ханс Бём в лекции Google I/O '17 " Как управлять собственной памятью C++ в Android ", я предлагаю использовать PhantomReference класс для обеспечения правильного удаления собственных пиров.

В связанном видео в 18 минут 57 секунд он показывает пример реализации объекта, регистрирующегося в PhantomReference класс для своего типа. это PhantomReference класс он показывает тогда в 19 мин 49 сек. Поэтому я скопировал его подход для моего примера объекта. Увидеть ниже.

Хотя этот подход работает нормально, он не масштабируется. Мне нужно будет создать довольно много объектов, и я не нашел способа создать базовый класс (ни для моих объектов, ни для 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 ответ

Решение

Вы можете взглянуть на 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<CleanerReference,Boolean>()) вместо.

Это сохранило deleteOrphanedNativePeerObjects() вызывать очистку явно, но это потокобезопасно, поэтому было бы без проблем создать фоновый поток демона для очистки элементов, как только они помещены в очередь, как в оригинале.

Выражая действие как Runnable позволяет использовать это для произвольных ресурсов, и получить Cleanable back позволяет поддерживать явную очистку, не полагаясь на сборщик мусора, при этом сохраняя сетку безопасности для тех объектов, которые не были закрыты.

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 -создание ресурсов, но также вызов close() вручную возможно, если нет простой области видимости блока. Преимущество его закрытия вручную заключается не только в том, что базовый ресурс закрывается намного раньше, но и из объекта-фантома. Set и никогда не будет поставлен в очередь, что сделает весь жизненный цикл более эффективным, особенно когда вы создаете и используете много объектов в короткие сроки. Но если close() не вызывается, уборщик попадает в очередь сборщиком мусора в конце концов.

Для классов, поддерживающих закрытие вручную, было бы полезно сохранить флаг, чтобы обнаруживать и отклонять попытки использовать его после закрытия.

Если лямбда-выражения недоступны для вашей цели, вы можете реализовать Runnable через внутренний класс; это все еще проще, чем создание другого подкласса фантомной ссылки. Необходимо соблюдать осторожность, чтобы не захватить this Например, поэтому создание было перемещено в static Метод в примере выше. Без this по объему, он не может быть захвачен случайно. Метод также объявляет референт как Object, чтобы обеспечить использование значений параметров вместо полей экземпляра.

Другие вопросы по тегам