WeakReferenced object is not garbage collected aft

2020-03-31 03:14发布

问题:

I am a fresh new learner of Java. I'm now learning the concept of WeakReference. I came across a problem which probably looks stupid but I just wanna figure out the reason. The problem is: according to Java doc, "Weak reference objects, which do not prevent their referents from being made finalizable, finalized, and then reclaimed."

So I did this small test:

import java.lang.ref.WeakReference; 

public class A {
    public static void main(String[] args) {
        A a = new A();
        WeakReference<A> wr = new WeakReference<>(a);
        a = null;

        A a1 = wr.get();

        System.out.println(a);
        System.out.println(a1);

        try {
            System.gc();

            Thread.sleep(10000);

        } catch (Exception e) {
            e.printStackTrace();
        }

        System.out.println(a1);
    }

    @Override
    protected void finalize( ) {
        System.out.println(Thread.currentThread().getName() + ": See ya, nerds!");
    }
}

However, I noticed that after GC running, wr.get() could still return object which I expected null, and the method finalize() was not invoked. So what went wrong? Thanks for your kind help in advance! :)

回答1:

The premise of your test is flawed. System.gc() is only a hint to run the garbage collector. It is frequently ignored.

From the documentation:

Calling the gc method suggests that the Java Virtual Machine expend effort toward recycling unused objects in order to make the memory they currently occupy available for quick reuse. When control returns from the method call, the Java Virtual Machine has made a best effort to reclaim space from all discarded objects.

(Emphasis mine)

In future, you may use the VM options -verbose:gc and -XX:+PrintGCDetails to see what the garbage collector is doing.


More importantly, you are also very quickly taking the reference out of the weak reference and putting it back into a strong reference:

A a = new A();
WeakReference<A> wr = new WeakReference<>(a);
a = null; // no strong references remain
A a1 = wr.get(); // the instance now has a strong reference again

Unless garbage collection occurs between these exact two instructions, the object will not be garbage collected.

If you remove a1, your code behaved as you would expect when I ran it (though, because of the first part of my answer, your mileage may vary):

class A
{
    public static void main(String[] args)
    {
        A a = new A();
        WeakReference<A> wr = new WeakReference<>(a);
        a = null;

        System.out.println(a);

        try {
            System.gc(); // instance of A is garbage collected
            Thread.sleep(10000);

        } catch (Exception e) {
            e.printStackTrace();
        }

        System.out.println(wr.get());
    }

    @Override
    protected void finalize( )
    {
        System.out.println(Thread.currentThread().getName() + ": See ya, nerds!");
    }
}


回答2:

Firstly, System.gc() does not ensure a garbage collection. Instead, it's just a hint that "It's a good time to run garbage collection".

Secondly, in your code when you put A a1 = wr.get(); before calling System.gc(), it creates a new strong reference to the same object referenced by a, thus even if garbage collection runs, your object will not be garbage collected.

As we have two tasks in hand

  1. Ensure garbage collection
  2. Don't keep any strong reference to the object you want to be garbage collected

Let's do little modification to your code

public class A {
    public static void main(String[] args) {
        A a = new A();
        WeakReference<A> wr = new WeakReference<>(a);
        a = null;
        // A a1 = wr.get(); Removing this, this does our 2nd task
        System.out.println(a);
        // System.out.println(a1); Removing this as a1 does not exists anymore
        try {
            while (null != wr.get()) { // 1st task done, the loop ensures sending the hint until your object collected
                System.gc();
                // Thread.sleep(10000); it does not have impact
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(wr.get()); // Obviously prints null
    }

    @Override
    protected void finalize() {
        System.out.println(Thread.currentThread().getName() + ": See ya, nerds!");
    }
}