3

When I run the following program

public static void main(String[] args) {

    ArrayList<Object> lists = new ArrayList<>();
    for (int i = 0; i <200000 ; i++) {
        lists.add(new Object());
    }
    System.gc();
    try {
        Thread.sleep(Integer.MAX_VALUE);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

and I dump the heap

jmap -dump:live,format=b,file=heap.bin 27648
jhat -J-Xmx2G heap.bin

The ArrayList and the 200000 objects are missing.

I don't know why the JVM knows that the objects will not be used and why the JVM judges that this GC root is not a reference.

Holger
  • 285,553
  • 42
  • 434
  • 765
K.shun
  • 33
  • 3
  • 1
    what exactly is your question? The 200000 object isn't missing, it never existed. if you want that to be instantiated as well, don't say < 200000, but <= 200000. If your question is: why doesn't the GC run, well ..; we can request the gc to run, but we can't make it run ourselves. – Stultuske Sep 21 '18 at 06:06
  • thank you , you can try it, I can't find the 200000 object in the localhost:7000 – K.shun Sep 21 '18 at 06:41
  • I just explained why your code doesn't create one – Stultuske Sep 21 '18 at 06:48
  • 2
    @Stultuske the OP didn’t ask about the 200000th object but about all 200000 objects. Granted, his grammar has room for improvements. However, your explanation is dead wrong anyway. When you iterate starting with zero with condition ` – Holger Sep 21 '18 at 07:26

1 Answers1

6

Local variables are not GC roots per se. The Java® Language Specification defines:

A reachable object is any object that can be accessed in any potential continuing computation from any live thread.

It’s obvious that it requires a variable holding a reference to an object, to make it possible to access it in a “potential continuing computation” from a live thread, so the absence of such variables can be used as an easy-to-check sign that an object is unreachable.

But this doesn’t preclude additional effort to identify unreachable objects which are still referenced by local variables. The specification even states explicitly

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.

in the same section.

Whether it actually does, depends on conditions like the current execution mode, i.e. whether the method runs interpreted or has been compiled already.

Starting with Java 9, you can insert explicit barriers, e.g.

public static void main(String[] args) {
    ArrayList<Object> list = new ArrayList<>();
    for (int i = 0; i <200000 ; i++) {
        list.add(new Object());
    }
    System.gc();
    try {
        Thread.sleep(Integer.MAX_VALUE);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    Reference.reachabilityFence(list);
}

This will force the list to stay alive.

An alternative for previous Java versions, is synchronization:

public static void main(String[] args) {
    ArrayList<Object> list = new ArrayList<>();
    for (int i = 0; i <200000 ; i++) {
        list.add(new Object());
    }
    System.gc();
    synchronized(list) {
        try {
            Thread.sleep(Integer.MAX_VALUE);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

But usually, you want unused objects to become collected as early as possible. Problems may only arise when you use finalize() together with naive assumptions about the reachability.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • See also “[finalize() called on strongly reachable object in Java 8](https://stackoverflow.com/q/26642153/2711488)” and “[Can java finalize an object when it is still in scope?](https://stackoverflow.com/q/24376768/2711488)” – Holger Sep 21 '18 at 08:11
  • 1
    While the answer provides good theoretical background about reachability etc., the explanation why ArrayList is collected in *this particular case* is not quite right. `jmap` does not make "an additional effort". `live` option just invokes the same GC cycle as `System.gc` - in this case the list will be collected even without `live`. Since the program has a long running loop, `main` method gets JIT-compiled, and after OSR replacement there is no more stack frame local that holds a reference to the list. – apangin Sep 21 '18 at 10:50
  • 1
    @apangin: OSR has its limitations in this regard, especially while the loop creating the objects is still running. I verified that the `System.gc()` call has no effect here, e.g. by monitoring the used heap and varying the time of triggering the heap dump. Of course, the “additional effort” consists of triggering a garbage collection and having OSR working, however, it only works here, because the `main` thread is in `sleep` to support the heap dump. E.g. when you replace `sleep` with a `while(true);` loop, the objects are not collected anymore. (Tested with `jdk1.8.0_65`) – Holger Sep 21 '18 at 11:26
  • *"System.gc() call has no effect here"* - It does. I'm not sure how you verified this, but the heap snapshot (taken with `jmap -F`) did not contain any large ArrayLists. – apangin Sep 21 '18 at 13:47
  • `while (true);` prevents compilation at a higher tier. `-XX:+PrintCompilation` will say something like "COMPILE SKIPPED: trivial infinite loop (retry at different tier)". That's why the list is not collected in this case. – apangin Sep 21 '18 at 13:50
  • 1
    @apangin as said, I monitored the heap, seeing no significant effect by the time `System.gc()` has been called, but a significant drop of used memory when by the time the heap dump was taken (varied that time in different runs). Now, granted, it is possible that were are other objects dominating the used heap not being collected in the first `System.gc()`, but collected when the heap dump was taken. By the way, even removing the `try catch` from the `sleep` and declaring `throws InterruptedException` at the main method instead, changed the outcome. – Holger Sep 21 '18 at 13:57