As Hans Boehm in the Google I/O '17 talk "How to Manage Native C++ Memory in Android" suggests I use the PhantomReferenceclass 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
CleanerAPI, 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 astaticregistration method. I recommend to keep the abstraction of the reference, i.e. theCleanableinterface, 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
Runnableallows to use this for arbitrary resources, and getting theCleanableback 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 theSetand 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
Runnablevia inner class; it’s still simpler than creating another subclass of the phantom reference. Care must be taken not to capture thethisinstance, that’s why the creation has been moved into astaticmethod in the example above. Without athisin 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.