Can deleting an unused field cause garbage collection?

For a library that includes asynchronous operations, I need to keep a reference to the object until a certain condition is met.

(I know this sounds unusual. So, here is some context, although it may not be strictly relevant: an object can be considered a direct ByteBuffer , which is used in JNI operations. JNI operations will retrieve the buffer address. At this point, this address is only a "pointer" that is not considered a reference to a byte buffer. The address can be used asynchronously, later in time. Thus, the buffer should be prevented from garbage collection until the JNI operation is complete.)

To achieve this, I applied 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 an instance of Runnable , which has the required object as a field, throws it into the executable file and allows runnable to wait until the condition is met. The contractor will reference the runnable instance until it is finished. Runnable is supposed to keep a reference to the required object. Thus, only after the condition is satisfied, the runnable will be released by the executor, and thus the local object will become suitable for garbage collection.

The localObject field localObject not used in the body of the run() method. Could this compiler (more precisely: runtime) detect this and decide to delete this unused link and thus make it too difficult to collect garbage earlier?

(I examined workarounds for this. For example, using an object in a "dummy statement", for example, logger.log(FINEST, localObject); but even then you couldn’t be sure that the smart optimizer won’t do some embedding and that’s all still discovering that the object is really not in use)


Update . As pointed out in the comments: whether this can work at all may depend on the exact implementation of the Executor (although I will have to analyze it more carefully). In this case, the executor will be ThreadPoolExecutor .

This may be one step to the answer:

ThreadPoolExecutor has an afterExecute method. You can override this method and then use the reflection sledgehammer to dive into the Runnable instance, which is given there as an argument. Now you can simply use reflective hacks to go to this link and use runnable.getClass().getDeclaredFields() to retrieve the fields (namely, the localObject field), and then get the value of this field. And I think that one should not be allowed to observe there a value that differs from that which it originally had.

Another comment noted that the default implementation afterExecute empty, but I'm not sure if this fact can affect the question of whether the field can be deleted or not.

Now I strongly suggest that the field may not be deleted. But some specific reference (or at least more convincing arguments) would be nice.


Update 2 . Based on Holger's comments and answer, I think that not deleting the "field itself" might be a problem, but rather the GC of the surrounding Runnable instance. So right now, I guess you can 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 localObject in runnable really lives as long as needed. But I almost can’t remember that I was ever forced to write something that shouted "hacking" as loudly as these few lines of code ...

+6
source share
2 answers

If the JNI code selects the direct buffer address, it must carry the JNI code itself to contain a reference to the direct buffer object, if the JNI code contains a pointer, for example. using NewGlobalRef and DeleteGlobalRef .

As for your specific question, this is addressed directly in JLS §12.6.1. Deployment Implementation :

Optimization of program transformations can be designed in such a way as to reduce the number of objects that are achievable, less than those that would be naively considered accessible ....

Another example of this occurs if the values ​​in the fields of objects are stored in registers .... Please note that such optimization is only allowed if the links are on the stack and not on the heap.

(last sentence matters)

This chapter illustrates an example that is not too different from yours. To make something short, the localObject reference in the Runnable instance will retain the lifetime of the reference object, at least as long as the lifetime of the Runnable instance.

However, the critical point here is the actual lifetime of the Runnable instance. It will be considered definitely alive, that is, immune to optimization, due to the above rule, if it also refers to an object that is immune to optimization, but even Executor not necessarily a globally visible object.

However, the inlining method is one of the simplest optimizations, after which the JVM detects that afterExecute a ThreadPoolExecutor is no-op. By the way, the passed Runnable Runnable passed to execute , but it will not be the same as the one passed to submit , if you use this method as (only) in the latter case, it is wrapped in RunnableFuture .

Note that even the current execution of the run() method does not prevent the collection of Runnable implementations, as shown in "finalize () called for a highly reachable object in Java 8" .

The bottom line is that you will walk on thin ice when you are trying to fight the garbage collector. The first sentence of the cited above says: "Optimization of program transformations can be designed to reduce the number of objects that are achievable, less than those that would be naively considered accessible." While we all may think that we think too naively ...

As stated at the beginning, you can review your responsibilities. It is worth noting that when your class has a close() method that must be called to free the resource after all threads have completed their work, this required explicit action is already enough to prevent the resource from being collected early (assuming the method is valid called at the right point) ...

+4
source

Running Runnable in a thread pool is not enough to prevent an object from collecting garbage. You can even put together "this"! See JDK-8055183 .

The following example shows that keepReference does not actually save it. Although the problem is with the vanilla JDK (because the compiler is not smart enough), it can be reproduced when a call to ThreadPoolExecutor.afterExecute called. This is an absolutely possible optimization because afterExecute does not work in the standard 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. You basically came up with a kind of Reach Capture , see JDK-8133348 .

The problem you are facing is known. It will be considered in Java 9 as part of JEP 193 . The standard API will explicitly mark objects as reachable: Reference.reachabilityFence(obj) .

Update

Javadoc comments to Reference.reachabilityFence suggest a synchronized block as an alternative construct for reachability.

+3
source

Source: https://habr.com/ru/post/1013154/


All Articles