0

Is there another way besides a fixed timeout to know when a WeakHashMap has updated its entries after a key becomes weakly reachable?

For example this code which nulls the strong reference to the key still prints that the entry is in the map unless a timeout is added.

    WeakHashMap<Integer, Integer> map = new WeakHashMap<>();
    Integer i = 1000;
    map.put(i, 1);
    System.out.println(map.size()); // prints 1
    i = null;
    System.gc();
    //Thread.sleep(0);
    System.out.println(map.size()); // without sleep prints 1, with sleep prints 0

Is there a more elegant way to know when the WeakReferences have finished updating?

Gonen I
  • 5,576
  • 1
  • 29
  • 60

1 Answers1

2

I don't think there is a good way to do it. There are a few problems:

  1. The cleaning of stale entries from the WeakHashMap is an internal mechanism that involves a ReferenceQueue that is private to the implementation. You would need to break encapsulation to access the queue field.

    OK that is possible, but you would be making your code Java version specific ... in theory.

  2. There is no good way for something other than WeakHashMap itself to inspect the queue. ReferenceQueue only provides three public operations: poll(), remove() and remove(time_out). These will all remove an element from the queue (if or when there is one). But if anything other than the WeakHashMap removes an element, that element won't be processed.

    This effectively means that you cannot detect when there is stuff on the queue without "breaking" it.

    OK so you could break abstraction on the ReferenceQueue as well to access its head field.

  3. Even if you do the above, you would still need to poll the ReferenceQueue.head field to detect when the queue becomes non-empty and empty again.

  4. The code in WeakHashMap that processes the queue does not run spontaneously. (There is no cleaner thread.) It actually runs when something performs an operation (e.g. get, put, size, clear and so on) on the WeakHashMap. The details are liable to be version specific.

    This means the "event" that your code is trying to detect is only going to happen when you call get. So if you only want to call get after the entry has been removed, you have a "loop" in the temporal dependencies.


Finally, it is not guaranteed that calling System.gc() will run immediately, (or at all), or that it will detect that given unreachable object is unreachable1. And even if it does detect this, it is not guaranteed that the WeakReference will be broken in a timely fashion.

So you should not code your application to depend on those things ... via predictable cleaning of the map. (And if the real use-case for this just unit testing, I don't think this is worth spending the time on this. The sleep workaround is fine ... assuming it is reliable enough.)

1 - For example, if the object has been tenured and the System.gc() call only triggers an minor collection, then the object would not be detected as unreachable.

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
  • 1
    In addition to the above, calling `System.gc()` *doesn't guarantee* garbage collector run immediately. Considering that garbage collection is a *very expensive operation*, it is up to the JVM to decide when is the right time to run it. Having a logic around garbage collection doesn't seem to be the best idea. – Akash Yadav Sep 05 '21 at 15:55
  • 1
    Even if `System.gc()` lets the GC run, there’s no guaranty that a single GC run collects all eligible objects. But even worse, the line `Integer i = 1000;` produces an object of unspecified identity. The runtime is permitted (even encouraged) to return a shared object that never gets collected. Or an object with a different GC policy, like not getting collected when being weakly reachable. And it’s generally not a good idea to use value-type like keys for an identity sensitive map like `WeakHashMap` or `IdentityHashMap`. The results can be … surprising. – Holger Sep 06 '21 at 08:35