Search code examples
javaandroidweak-references

Java / weak references : weak reference.get() is not null while there is no any more strong reference holding it


Here is my very simple code :

class UpdateAlertActivity extends Activity {
    O o ;
    WeakReference<O> _o ;
    ArrayList<O> arr = new ArrayList<>();

    static class O {
        private String l ;

        public O(String l) {
            this.l = l ;
        }
    }

    void test()
    {
        arr.clear();
        Runtime.getRuntime().gc();
        Log.i(LOG_TAG, "breakpoint");
    }

    @Override
    protected void onResume()
    {
        super.onResume();

        o = new O("hello");
        arr.add(o);

        _o = new WeakReference<>(o);


        test(); 
    }
}

As I clear array in test() method, I was excepting _o.get() to be null at my breakpoint, but it's not the case. When I see in the debbuger where o is held, it shows me only in the weak reference _o, while I thought weak reference are made to free their instance when there is no other strong reference on them...

I saw on StackOverflow that I missed calling GC, but calling it had no effect in my program as you can see.

EDIT : Even this super simple code does not work as excpected, it makes no sense...

O o = new O("lol");

WeakReference<O> _o = new WeakReference<>(o);

Log.i(LOG_TAG, "1:" + _o.get().toString());

o = null ;

Log.i(LOG_TAG, "2:" + _o.get().toString());

System.gc();
Log.i(LOG_TAG, "3:" + _o.get().toString()); // output not null here 

EDIT2 : I wanted the use of GC because I have, in another static class, an array called lastUpdates, which is filled with app updates sent by my server wia TCP, sometimes.

All my activities observes this lastUpdate array (by implementing Observer/Observable interface), and when it is changed, all are notified, and call a particular function onUpdate(ArrayList u) with the list of new arrived updates as a parameter. This function process the updates only if the activity is resumed (changing UI during activity sleep causes app to crash).

If the entiere app is "sleeping" (user is in device menu for example), I want only the first activity waking up to be able to process new updates.

For that, I created in my activity class a array pendingPersistentUpdates storing weak references of all updates that has been catch when activity was sleeping. When the first activity wakes up, the updates catch during app sleep time are still in lastUpdates array, so I expect the weak references stored in pendingPersistentUpdates activity prop to return the actual updates, so my activity can UI-process them, at onResume.

I was expecting this situation :

  • An activity A run, an activity B is paused (behind activity A for example)
  • App receives an update U. All activities (A and B) are notified. A process the update because it's running, as expected. B store this update in its pendingPersistentUpdates, because it's paused.
  • A pause, user returns to B. At A pause, lastUpdates array is cleared, so the weak references of the updates in B pendingPersistentUpdates would return null
  • B resume, but pendingPersistentUpdates weak references are null (lastUpdates has been cleared), so U is not processed.

Though, as lastUpdate.clear() does not fire GC, B pendingPersistentUpdates updates still exist, and are processed a second time (behavior not desired)


Solution

  • It is not possible to force the garbage collector to run. Runtime.getRuntime().gc() is just an alias for System.gc(), and both are just a hint (read the docs; they mention this). In particular, calling gc() usually means the collection will occur in some other thread; so it's more the 'start sign' for the collector; gc() doesn't necessarily pause and wait around for the gc to finish a full cycle, it merely tells the gc to start one. Whilst, again, no guarantee, Thread.sleep(10000L); makes it more likely you'll see the effects of the GC call.

    A WeakReference merely guarantees that the existence of this object won't impede on any garbage collection of the object it refers to. That's all. It doesn't instantaneously lose its referent when the only way to get to the referent is via WeakReference objects. That'll happen later (in fact, that will happen before the object is GCed: First the collector will wipe out all weakrefs, and sometime later will the memory that the object occupied be truly free for use). So, what you're observing in your debugger (that the only way to get to the object is via that WR) isn't inherently broken.

    Note also that the debugger itself can be the issue. Merely being there and observing it can have an effect (so make sure you run it without as well).

    If you want the GC to try harder, you can allocate some fairly large arrays and pause the thread, that can help, but there is nothing you can do to guarantee a GC run. If you want to observe, run java with java -verbose:gc restOfArgsHere, and keep an eye on the console; if a GC actually occurs, you'll see it.

    However.

    Your code as pasted doesn't even compile which suggests you either pasted only half of it, or you edited it some 'for clarity' (often a bad idea, best to paste precisely what you're working with!). In particular, you write o = new O("hello");, but o isn't defined anywhere. If it is a field of your class, it would imply the referent cannot be collected at all! So you might want to check on that. Make that O o = new O("hello"); instead of what you have. Assuming you read your debugger correctly and it's functioning properly, this isn't it, but it is suspicious.