For a library that involves asynchronous operations, I have to keep a reference to an object alive until a certain condition is met.
(I know, that sounds unusual. So here is some context, although it may not strictly be relevant: The object may be considered to be a direct ByteBuffer
which is used in JNI operations. The JNI operations will fetch the address of the buffer. At this point, this address is only a "pointer" that is not considered as a reference to the byte buffer. The address may be used asynchronously, later in time. Thus, the buffer has to be prevented from being garbage collected until the JNI operation is finished.)
To achieve this, I implemented a method that is basically equivalent to this:
private static void keepReference(final Object object)
{
Runnable runnable = new Runnable()
{
@SuppressWarnings("unused")
private Object localObject = object;
public void run()
{
// Do something that does NOT involve the "localObject" ...
waitUntilCertainCondition();
// When this is done, the localObject may be garbage collected
}
};
someExecutor.execute(runnable);
}
The idea is to create a Runnable
instance that has the required object as a field, throw this runnable into an executor, and let the runnable wait until the condition is met. The executor will keep a reference to the runnable instance until it is finshed. The runnable is supposed to keep a reference to the required object. So only after the condition is met, the runnable will be released by the executor, and thus, the local object will become eligible for garbage collection.
The localObject
field is not used in the body of the run()
method. May the compiler (or more precisely: the runtime) detect this, and decide to remove this unused reference, and thus allow the object to be garbage collected too early?
(I considered workarounds for this. For example, using the object in a "dummy statement" like logger.log(FINEST, localObject);
. But even then, one could not be sure that a "smart" optimizer wouldn't do some inlining and still detect that the object is not really used)
Update: As pointed out in the comments: Whether this can work at all might depend on the exact Executor
implementation (although I'd have to analyze this more carefully). In the given case, the executor will be a ThreadPoolExecutor
.
This may be one step towards the answer:
The ThreadPoolExecutor
has an afterExecute
method. One could override this method and then use a sledgehammer of reflection to dive into the Runnable
instance that is given there as an argument. Now, one could simply use reflection hacks to walk to this reference, and use runnable.getClass().getDeclaredFields()
to fetch the fields (namely, the localObject
field), and then fetch the value of this field. And I think that it should not be allowed to observe a value there that is different from the one that it originally had.
Another comment pointed out that the default implementation of afterExecute
is empty, but I'm not sure whether this fact can affect the question of whether the field may be removed or not.
Right now, I strongly assume that the field may not be removed. But some definite reference (or at least more convincing arguments) would be nice.
Update 2: Based on the comments and the answer by Holger, I think that not the removal of "the field itself" may be a problem, but rather the GC of the surrounding Runnable
instance. So right now, I assume that one could try something like this:
private static long dummyCounter = 0;
private static Executor executor = new ThreadPoolExecutor(...) {
@Override
public void afterExecute(Runnable r, Throwable t) {
if (r != null) dummyCounter++;
if (dummyCounter == Long.MAX_VALUE) {
System.out.println("This will never happen", r);
}
}
}
to make sure that the localObject
in the runnable really lives as long as it should. But I can hardly remember ever having been forced to write something that screamed "crude hack" as loud as these few lines of code...