Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

May the removal of an unused field cause a garbage collection?

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...

like image 229
Marco13 Avatar asked Dec 14 '16 16:12

Marco13


People also ask

Which of the following statements about a garbage collector is true?

D. Objects that can be reached from a live thread will never be garbage collected. Explanation: Option D is correct.

What is garbage collection and how it works?

As long as an object is being referenced, the JVM considers it alive. Once an object is no longer referenced and therefore is not reachable by the application code, the garbage collector removes it and reclaims the unused memory.

What part of memory is cleaned in garbage collection?

Garbage collection makes Java memory efficient because it removes the unreferenced objects from heap memory and makes free space for new objects.


1 Answers

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)…

like image 64
Holger Avatar answered Sep 21 '22 00:09

Holger