System.gc() collects objects still referenced by local variables

K.shun :

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 :

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.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=92319&siteId=1