import java.util.concurrent.TimeUnit; public class LocalCollect { private static volatile boolean alwaysTrue = true; public static void main(String... args) throws InterruptedException { Object o = new byte[10000000]; sink(o); while (alwaysTrue) { System.gc(); TimeUnit.SECONDS.sleep(1); // safepoints somewhere here in the loop } // sink(o); } public static void sink(Object o) { // Confuse compiler into believing the object is used. // This method should not be inlined to work. } /* This test demonstrates the VM ability to collect local objects without leaving methods. While the common "wisdom" is that locally referred objects are only collected when the method exits (i.e. local variable goes out of scope), the actual reclamation may happen much sooner. Obligatory JLS reference: http://docs.oracle.com/javase/specs/jls/se8/html/jls-12.html#jls-12.6.1 "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. For example, a Java compiler or code generator may choose to set a variable or parameter that will no longer be used to null to cause the storage for such an object to be potentially reclaimable sooner." The test is rather intricate to run. First, it should be run with -XX:-Inline in order to escape dead-code elimination of the object we have allocated. Our makeshift sink() method only works if not inlined. While this confuses compiler enough to keep the allocation itself, it would not confuse GC which will traverse the actual heap. Second, it should be run with -Xcomp, to let compiler do its work without letting the user code to be stuck in interpreted mode. OSR helps here, but does not show the effect we are after. We need compiler to do some liveness analysis for us here, and prove that at safepoint in the loop, the local "o" is not needed anymore, and therefore there is no sense to add it to the root set. Third, you can observe the effect by watching -verbose:gc. This is the most non-intrusive way to demonstrate the effect. We run this test on Linux x86_64, JDK 8u40 EA. Let us first run with interpreter to see what the basic case looks like: $ java -Xint -verbose:gc LocalCollect [GC (System.gc()) 14926K->10181K(494592K), 0.0093005 secs] [Full GC (System.gc()) 10181K->10013K(494592K), 0.0066671 secs] [GC (System.gc()) 15174K->10141K(494592K), 0.0004648 secs] [Full GC (System.gc()) 10141K->10014K(494592K), 0.0042075 secs] See, we allocated the 10MB array, and it's still lingering in the heap. Now, run with the "proper" test configuration: $ java -XX:-Inline -Xcomp -verbose:gc LocalCollect [GC (System.gc()) 22668K->424K(494592K), 0.0084817 secs] [Full GC (System.gc()) 424K->285K(494592K), 0.0133630 secs] See, the array had been collected, even though we haven't left the method! It works with C1 alone as well: $ java -XX:-Inline -Xcomp -XX:TieredStopAtLevel=1 -verbose:gc LocalCollect [GC (System.gc()) 17507K->376K(494592K), 0.0006936 secs] [Full GC (System.gc()) 376K->255K(494592K), 0.0117351 secs] [GC (System.gc()) 7997K->327K(494592K), 0.0097069 secs] [Full GC (System.gc()) 327K->257K(494592K), 0.0056760 secs] Now, if you *uncomment* the second sink(o) that extends the local variable lifetime after the loop where we are stuck, then you will see: $ java -XX:-Inline -Xcomp -verbose:gc LocalCollect [GC (System.gc()) 25249K->10189K(494592K), 0.0045950 secs] [Full GC (System.gc()) 10189K->10051K(494592K), 0.0110617 secs] [GC (System.gc()) 17792K->10187K(494592K), 0.0022305 secs] [Full GC (System.gc()) 10187K->10052K(494592K), 0.0159613 secs] The local variable is now presumed alive at safepoints in the loop, and so we keep its contents around. Aside: One can note this may cause a premature finalization of the object. Indeed, this is one of the reasons why would one want a reachabilityFence that forces object to "survive" till the end of the method: http://gee.cs.oswego.edu/dl/jsr166/dist/docs/java/util/concurrent/atomic/Fences.html */ }