As Hans Boehm in the Google I/O '17 talk "How to Manage Native C++ Memory in Android" suggests I use the PhantomReference
class to ensure native peers are deleted properly.
In the linked video at 18 min 57 sec he shows an example implementation of an object registering itself to the PhantomReference
class for it's type. This PhantomReference
class, he shows then at 19 min 49 sec. So I copied his approach for my example object. See below.
While this approach works fine, it does not scale. I will need to create quite some amount of objects and I haven't found a way to create a base class (either for my objects or a PhantomReference
base class) which would take any objects and would handle the native deletion properly.
How can I make a generic base PhantomReference
class which can call the native static method on the provided object?
I've tried to transform the PhantomReference
generic but the native static deletion method hinders an implementation.
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);
}
}
}
You may have a look at Java 9’s
Cleaner
API, which addresses a similar task, cleanup built around aPhantomReference
, and implement a similar thing, adapted it to your needs. Since you don’t need to support multiple cleaners, you can stay with astatic
registration method. I recommend to keep the abstraction of the reference, i.e. theCleanable
interface, to ensure that no inherited reference method can be invoked, especially asclear()
andclean()
are easy to confuse:This uses Java 8 features; if
ConcurrentHashMap.newKeySet()
is not available, you may useCollections.newSetFromMap(new ConcurrentHashMap<CleanerReference,Boolean>())
instead.It kept the
deleteOrphanedNativePeerObjects()
to trigger cleanup explicitly, but it is thread safe, so it would be no problem to create a daemon background thread to clean items as soon as they are enqueued, like in the original.Expressing the action as
Runnable
allows to use this for arbitrary resources, and getting theCleanable
back allows to support explicit cleanup without relying on the garbage collector while still having the safety net for those objects that haven’t been closed.By implementing
AutoCloseable
, it can be used with thetry
-with-resources construct, but also invokingclose()
manually is possible, if there is not a simple block scope. The advantage of closing it manually, is not only that the underlying resource gets closed much earlier, also the phantom object is removed from theSet
and will never be enqueued, making the entire life cycle more efficient, especially when you create and use a lot objects in short terms. But ifclose()
is not called, the cleaner gets enqueued by the garbage collector eventually.For classes supporting to be closed manually, keeping a flag would be useful, to detect and reject attempts to use it after being closed.
If lambda expressions are not available for your target, you can implement
Runnable
via inner class; it’s still simpler than creating another subclass of the phantom reference. Care must be taken not to capture thethis
instance, that’s why the creation has been moved into astatic
method in the example above. Without athis
in scope, it can’t be captured by accident. The method also declares the referent asObject
, to enforce the usage of the parameter values instead of instance fields.