2017-09-10 33 views
6

Ponieważ Hans Boehm w rozmowie Google I/O '17 "How to Manage Native C++ Memory in Android" sugeruje użycie klasy PhantomReference w celu zapewnienia prawidłowego usunięcia natywnych urządzeń równorzędnych.Usuwanie rodzimych obiektów równorzędnych z ogólną klasą PhantomReference

W połączonym wideo pod numerem 18 min 57 sec pokazuje przykładową implementację obiektu rejestrującego się w klasie PhantomReference dla jego typu. Ta klasa PhantomReference pokazuje wtedy pod numerem 19 min 49 sec. Więc skopiowałem jego podejście do mojego przykładowego obiektu. Zobacz poniżej.

Chociaż to podejście działa dobrze, nie skaluje się. Będę musiał utworzyć całkiem sporo obiektów i nie znalazłem sposobu na utworzenie klasy bazowej (dla moich obiektów lub klasy bazowej PhantomReference), która zajmowałaby dowolne obiekty i poprawnie obsługiwałaby natywne usuwanie.

Jak utworzyć podstawową klasę podstawową PhantomReference, która może wywoływać natywną metodę statyczną na podanym obiekcie?

Próbowałem przekształcić generic PhantomReference, ale natywna metoda usuwania statycznego przeszkadza w implementacji.

My 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); 
} 

My 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); 
    } 
    } 
} 
+0

Wierzę, że od razu takie podejście nie jest skalowane. Ale nie rozumiem twojego drugiego problemu, który "nie znalazłeś sposobu na stworzenie klasy bazowej ... która zajmowałaby jakiekolwiek obiekty i poprawnie obsługiwałaby natywne usuwanie *". Masz działające rozwiązanie składające się z dwóch klas. Jaki problem powinna rozwiązać ta hipotetyczna klasa podstawowa? – Holger

+0

@Holger dzięki za odpowiedź. Proszę wyjaśnić mi kwestię skali? To właśnie próbuję rozwiązać za pomocą hipotetycznej klasy bazowej. Mam wiele takich obiektów i dla każdego z nich muszę stworzyć drugą klasę widmową. Z klasą bazową chciałbym rozwiązać, że nie musiałbym tworzyć dodatkowej klasy lub uczynić ją tak prostą, że wystarczy tylko zdefiniować typ. –

+0

Czekaj - tworzysz nową klasę fantomów dla każdego tworzonego obiektu? Albo co masz na myśli mówiąc "każdy z nich"? – Holger

Odpowiedz

5

Możesz rzucić okiem na Java 9.x za Cleaner API, które rozwiązuje podobne zadanie, oczyszczanie zbudowany wokół PhantomReference i zaimplementować coś podobnego , dostosowałem go do twoich potrzeb. Ponieważ nie trzeba obsługiwać wielu urządzeń czyszczących, można pozostać przy metodzie rejestracji static. Polecam, aby utrzymać pobór odniesienia, to znaczy interfejs Cleanable, aby upewnić się, że nie odziedziczyła metoda referencyjna może być wywołany, zwłaszcza clear() i clean() są łatwo pomylić:

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(); 
     } 
    } 
} 

używa Javy 8 możliwości; jeśli ConcurrentHashMap.newKeySet() nie jest dostępny, możesz zamiast tego użyć Collections.newSetFromMap(new ConcurrentHashMap<CleanerReference,Boolean>()).

Utrzymuje, że deleteOrphanedNativePeerObjects() powoduje wyraźne wyleczenie, ale jest bezpieczny dla wątków, więc nie byłoby problemu z utworzeniem wątku tła demona w celu czyszczenia elementów zaraz po ich wstawieniu, tak jak w oryginale.

Wyrażając działanie jako Runnable, można użyć tego do arbitralnych zasobów, a odzyskanie Cleanable pozwala na wsparcie czyszczenia jawnego bez polegania na śmieciu, gdy wciąż ma sieć bezpieczeństwa dla tych obiektów, które nie zostały zamknięte.

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); 

} 

Realizując AutoCloseable, może być używany z try-with-resources konstrukcji, a także wywoływanie close() ręcznie jest możliwe, jeśli nie jest to prosty zakres blokowego. Zaletą ręcznego zamykania jest nie tylko to, że zasób bazowy zostanie zamknięty znacznie wcześniej, ale także obiekt widmowy zostanie usunięty z Set i nigdy nie zostanie zapisany w kolejce, co sprawi, że cały cykl życia stanie się bardziej efektywny, zwłaszcza gdy stworzysz i użyjesz dużo obiekty w krótkich terminach. Ale jeśli nie zostanie wywołane close(), urządzenie czyszczące zostanie w końcu zgrupowane przez urządzenie do zbierania śmieci.

Dla klas, które można zamknąć ręcznie, zachowanie flagi byłoby użyteczne, aby wykryć i odrzucić próby jej użycia po zamknięciu.

Jeśli wyrażenia lambda nie są dostępne dla twojego celu, możesz zaimplementować Runnable przez wewnętrzną klasę; jest jeszcze prostsze niż stworzenie kolejnej podklasy odniesienia fantomu. Należy uważać, aby nie uchwycić instancji this, dlatego kreacja została przeniesiona do metody static w powyższym przykładzie. Bez zakresu this nie można go przechwycić przypadkowo. Metoda deklaruje również odniesienie jako Object, aby wymusić użycie wartości parametrów zamiast pól instancji.

+0

Działa jak urok. Dziękuję Ci bardzo. Zastąpiłem 'Runnable' przez własną klasę, ponieważ' Runnable' bardzo często odnosi się do używania wątków i nie chciałem wprowadzać żadnego możliwego zamieszania na ten temat. –

+0

Musiałem użyć wywołania 'Collections.newSetFromMap (nowe ConcurrentHashMap ())', ponieważ docelowa wersja dla aplikacji dla systemu Android jest na poziomie interfejsu API 15. Wystąpił następujący problem https://stackoverflow.com/ a/25705596/1306012 –

+0

Cóż, 'Collections.newSetFromMap (...)' było tym, co zasugerowałem również w mojej odpowiedzi na środowisko pre-Java 8. Zwróć uwagę na różnicę między 'newKeySet()' i 'keySet()': pierwszy jest specjalną metodą 'ConcurrentHashMap', która istnieje tylko w Java 8 lub nowszej, druga to ogólna metoda' Map', która zwraca ustawiony widok do klucze mapy i nie obsługuje operacji 'add', więc i tak nie byłoby to właściwe. – Holger