May the removal of an unused field cause a garbage collection?
If JNI code fetches the address of a direct buffer, it should be the responsibility of the JNI code itself, to hold a reference to the direct buffer object as long as the JNI code holds the pointer, e.g. using NewGlobalRef
and DeleteGlobalRef
.
Regarding your specific question, this is addressed directly in JLS §12.6.1. Implementing Finalization:
Optimizing transformations of a program can be designed that reduce the number of objects that are reachable to be less than those which would naively be considered reachable. …
Another example of this occurs if the values in an object's fields are stored in registers. … Note that this sort of optimization is only allowed if references are on the stack, not stored in the heap.
(the last sentence matters)
It is illustrated in that chapter by an example not too different to yours. To make things short, the localObject
reference within the Runnable
instance will keep the life time of the referenced object at least as long as the life time of the Runnable
instance.
That said, the critical point here is the actual life time of the Runnable
instance. It will be considered definitely alive, i.e. immune to optimizations, due to the rule specified above, if it is also referred by an object that is immune to optimizations, but even an Executor
isn’t necessarily a globally visible object.
That said, method inlining is one of the simplest optimizations, after which a JVM would detect that the afterExecute
of a ThreadPoolExecutor
is a no-op. By the way, the Runnable
passed to it is the Runnable
passed to execute
, but it wouldn’t be the same as passed to submit
, if you use that method, as (only) in the latter case, it’s wrapped in a RunnableFuture
.
Note that even the ongoing execution of the run()
method does not prevent the collection of the Runnable
implementation’s instance, as illustrated in “finalize() called on strongly reachable object in Java 8”.
The bottom line is that you will be walking on thin ice when you try to fight the garbage collector. As the first sentence of the cite above states: “Optimizing transformations of a program can be designed that reduce the number of objects that are reachable to be less than those which would naively be considered reachable.” Whereas we all may find ourselves being thinking too naively…
As said at the beginning, you may rethink the responsibilities. It’s worth noting that when your class has a close()
method which has to be invoked to release the resource after all threads have finished their work, this required explicit action is already sufficient to prevent the early collection of the resource (assuming that the method is indeed called at the right point)…
Execution of Runnable
in a thread pool is not enough to keep an object from being garbage collected. Even "this" can be collected! See JDK-8055183.
The following example shows that keepReference
does not really keep it. Though the problem does not happen with vanilla JDK (because the compiler is not smart enough), it can be reproduced when a call to ThreadPoolExecutor.afterExecute
is commented out. It is absolutely possible optimization, because afterExecute
is no-op in the default ThreadPoolExecutor
implementation.
import java.lang.ref.WeakReference;import java.util.concurrent.*;public class StrangeGC { private static final ExecutorService someExecutor = Executors.newSingleThreadExecutor(); private static void keepReference(final Object object) { Runnable runnable = new Runnable() { @SuppressWarnings("unused") private Object localObject = object; public void run() { WeakReference<?> ref = new WeakReference<>(object); if (ThreadLocalRandom.current().nextInt(1024) == 0) { System.gc(); } if (ref.get() == null) { System.out.println("Object is garbage collected"); System.exit(0); } } }; someExecutor.execute(runnable); } public static void main(String[] args) throws Exception { while (true) { keepReference(new Object()); } }}
Your hack with overriding afterExecute
will work though.
You've basically invented a kind of Reachability Fence, see JDK-8133348.
The problem you've faced is known. It will be addressed in Java 9 as a part of JEP 193. There will be a standard API to explicitly mark objects as reachable: Reference.reachabilityFence(obj)
.
Update
Javadoc comments to Reference.reachabilityFence
suggest synchronized
block as an alternative construction to ensure reachability.