I am implementing a feature that reports an error when instances of my Java class are discarded before being "used" (for simplicity, we can define being "used" as having a particular method called).
My first idea is to use phantom references, which is often used as an improvement on finalize()
methods. I would have a phantom reference class that would point to my main object (the one I want to detect whether it is discarded before being used) as the referrent, something like this:
class MainObject {
static class MyPhantom extends PhantomReference<MainObject> {
static Set<MyPhantom> phantomSet = new HashSet<>();
MyPhantom(MainObject obj, ReferenceQueue<MainObject> queue) {
super(obj, queue);
phantomSet.add(this);
}
void clear() {
super.clear();
phantomSet.remove(this);
}
}
MyPhantom myPhantom = new MyPhantom(this, referenceQueue);
static ReferenceQueue<MainObject> referenceQueue = new ReferenceQueue<>();
void markUsed() {
myPhantom.clear();
myPhantom = null;
}
static void checkDiscarded() { // run periodically
while ((aPhantom = (MyPhantom) referenceQueue.poll()) != null) {
aPhantom.clear();
// do stuff with aPhantom
}
}
}
However, I am using Java 8, and in Java 8, phantom references are not automatically cleared when they are enqueued into the reference queue. (I know that this is fixed in Java 9, but unfortunately, I must use Java 8.) This means that, once the GC determines that the main object is not strongly reachable, and enqueues the phantom reference, it still cannot reclaim the memory of the main object, until I manually clear the phantom reference after I dequeue it in checkDiscarded()
. I am concerned that, during the period of time between the GC enqueuing the phantom reference and me dequeueing it from the queue, the main object will remain in memory when it's unnecessary. My main object references many other objects which take a lot of memory, so I would not want it staying in memory for longer than without this feature.
To avoid this problem of the phantom reference preventing the main object from being reclaimed, I came up with the idea of using a dummy object as the referrent of the phantom reference instead of my main object. This dummy object will be referenced from my main object, so it will become unreachable at the same time as my main object. Since the dummy object will be small, I don't mind it not being reclaimed for longer period of time, as long as my main object will be reclaimed as soon as it's not reachable. Does this seem like a good idea, and is it really better than using the main object as the referrent?
class MainObject {
static class MyPhantom extends PhantomReference<Object> {
static Set<MyPhantom> phantomSet = new HashSet<>();
MyPhantom(Object obj, ReferenceQueue<Object> queue) {
super(obj, queue);
phantomSet.add(this);
}
void clear() {
super.clear();
phantomSet.remove(this);
}
}
Object dummyObject = new Object();
MyPhantom myPhantom = new MyPhantom(dummyObject, referenceQueue);
static ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
void markUsed() {
myPhantom.clear();
myPhantom = null;
}
static void checkDiscarded() { // run periodically
while ((aPhantom = (MyPhantom) referenceQueue.poll()) != null) {
aPhantom.clear();
// do stuff with aPhantom
}
}
}
Another idea I am considering is to use weak references instead of phantom references. Unlike phantom references in Java 8, weak references are cleared when they are enqueued, so it does not prevent the referrent from being reclaimed. I understand that the reason why phantom references are usually used for resource cleanup, is that phantom references are only enqueued after the referrent is finalized and guaranteed to not be used anymore, whereas weak references are enqueued before being finalized, and so resources cannot be freed yet, and also the finalizer might resurrect the object. However, that's not a concern in my case, as I am not "cleaning up" any resources, but just making a report that my main object was discarded before being used, which can be done while the object is still in memory. My main objects also do not have a finalize()
method, so there is no concern of resurrecting the object. So do you think weak references would be a better match for my case?