How does a value in an entry in the WeakHashMap gets garbage collected when the actual object is garbage collected?
Asked Answered
O

1

6

First of all I would like to clarify my understanding of the WeakReference as the following question depends on the same.

static void test() {
    Person p = new Person();
    WeakReference<Person> person = new WeakReference<>(p);
    p = null;
    System.gc();
    System.out.println(person.get());
    System.out.println(person);
}

static class Person {
    String name;
}

static class PersonMetadata {
    String someData;

    public PersonMetadata(String met) {
        someData = met;
    }
}

The output of the above code is

null java.lang.ref.WeakReference@7852e922

Which means that although there is the actual person object is garbage collected once a GC runs, there is an object of WeakReference<Person> class is there in the memory which does not point to anything at this point.

Now considering the above understanding true, I am confused about how does WeakHashMap<K,V> works. In the below code

public static void main(String[] args) {
    Person p = new Person();
    p.name = "John";
    WeakHashMap<Person, PersonMetadata> map = new WeakHashMap<>();
    PersonMetadata meta = new PersonMetadata("Geek");
    map.put(p, meta);
    p = null;
    System.gc();
    if (map.values().contains(meta)) {
        System.out.println("Value present");
    } else {
        System.out.println("Value gone");
    }
}

static class Person {
    String name;
}

static class PersonMetadata {
    String someData;

    public PersonMetadata(String met) {
        someData = met;
    }
}

OUTPUT: Value gone

Now the the question is as it is said that the key in WeakHashMap<K,V> is an weak reference which means that in the above code when p becomes null the actual object can be garbage collected as there is no more strong reference to the object, but how does the and the value which is an object of PersonMetadata class is getting garbage collected as the first code proves that object of WeakReference class is not garbage collected even though the actual object is collected.

Occasionally answered 4/6, 2018 at 18:45 Comment(1)
I've explained the way to cleanup weak references answering this question.Magnetoelectricity
M
7

You are misunderstanding the situation. When map.values().contains(meta), or short map.containsValue(meta) returns false, it doesn’t imply that meta has been garbage collected. In fact, you are holding a reference to the object in meta and even passing that reference to the contains method which may invoke equals on it. So how could that object be garbage collected?

The response only tells you that there is no association from one of the map’s keys to that object and since the only key has been garbage collected, that’s the correct answer. Alternatively, you could just have asked map.isEmpty() to check for the presence of the association.

This is what the WeakHashMap provides:

Hash table based implementation of the Map interface, with weak keys. An entry in a WeakHashMap will automatically be removed when its key is no longer in ordinary use. More precisely, the presence of a mapping for a given key will not prevent the key from being discarded by the garbage collector, that is, made finalizable, finalized, and then reclaimed. When a key has been discarded its entry is effectively removed from the map, so this class behaves somewhat differently from other Map implementations.

The removal of the entry is not instantaneous. It relies on enqueuing of the WeakReference into a ReferenceQueue, which is then polled internally when you make the next query, like containsValue or even size(). E.g. if I change your program to:

Person p = new Person();
WeakHashMap<Person, PersonMetadata> map = new WeakHashMap<>();
PersonMetadata meta = new PersonMetadata("Geek");
map.put(p, meta);
WeakReference<?> ref = new WeakReference<>(p);
p = null;
while(ref.get() != null) System.gc();
System.out.println(map.containsValue(meta)? "Value present": "Value gone");

It occasionally prints “Value present” despite the key Person instance provenly has been garbage collected at this point. As said above, this is about the map’s internal cleanup, not about the PersonMetadata instance to which we’re holding a strong reference in meta anyway.

Making PersonMetadata eligible to garbage collection is an entirely different thing. As said, the WeakReference does an internal cleanup whenever we call a method an it. If we don’t, there will be no cleanup and hence, still a strong reference, even if the key has been garbage collected. Consider:

Person p = new Person();
WeakHashMap<Person, PersonMetadata> map = new WeakHashMap<>();
PersonMetadata meta = new PersonMetadata("Geek");
map.put(p, meta);
WeakReference<?> personRef = new WeakReference<>(p);
WeakReference<?> metaRef = new WeakReference<>(meta);
p = null;
meta = null;
while(personRef.get() != null) System.gc();
System.out.println("Person collected");
for(int i = 0; metaRef.get() != null && i < 10; i++) {
    System.out.println("PersonMetadata not collected");
    System.gc();
    Thread.sleep(1000);
}
System.out.println("calling a query method on map");
System.out.println("map.size() == "+map.size());
System.gc();
System.out.println("PersonMetadata "+(metaRef.get()==null? "collected": "not collected"));

Which will print

Person collected
PersonMetadata not collected
PersonMetadata not collected
PersonMetadata not collected
PersonMetadata not collected
PersonMetadata not collected
PersonMetadata not collected
PersonMetadata not collected
PersonMetadata not collected
PersonMetadata not collected
PersonMetadata not collected
calling a query method on map
map.size() == 0
PersonMetadata collected

demonstrating how the WeakHashMap holds a strong reference to the value of an already collected key until we eventually invoke a method on it, to give it a chance to perform its internal cleanup.

The value finally gets collected when neither, the WeakHashMap nor our method, hold a reference on it. When we remove the meta = null; statement, the map still will be empty at the end (after its internal cleanup), but the value won’t be collected.

It’s important to keep in mind that these code examples are for demonstration purposes and touch implementation specific behavior, most notably, that a main method usually runs unoptimized. Formally, local variables are not required to prevent garbage collection if the referent is otherwise unused, a point which has relevance in practice when methods have been optimized.

Madsen answered 5/6, 2018 at 13:33 Comment(6)
Thanks @Madsen thanks for this valuable input, the part which has clarified my doubt is The removal of the entry is not instantaneous. It relies on enqueuing of the WeakReference into a ReferenceQueue. I am accepting this as the answer.Occasionally
I see it is only for demonstration, but I have a couple of questions 1)Is it possible that the last System.gc might not result in the collection of meta and the last line print not collected (after WeakHashMap internal clean up) (since we are not looping till metaRef.get()==null and I feel that we are just giving it one chance). 2) Theoretically can while(personRef.get() != null) System.gc(); result in an infinite loop?Fearnought
@user7 yes, that’s for demonstration only and assuming a JVM like HotSpot in its default configuration where System.gc() is respected and sufficient for cleaning the reference. In other environments, the last line could print “not collected” and while(personRef.get() != null) System.gc(); could loop forever. In these environments, the example code wouldn’t be sufficient to demonstrate the workings of WeakHashMap. In fact, it would be impossible to create any code for demonstrating its behavior for such environments.Madsen
Thanks for the clarification :)Fearnought
@Madsen there is a tiny question that I have for the first snippet, how come I sometimes see Value Present? Does that mean the publishing to that ReferenceQueue is not instant? because internally once containsValue is called, expungeStaleEntries is called also...Arcade
@Arcade yes, publishing to the ReferenceQueue is not instantaneous, at least not on HotSpot/OpenJDK. The garbage collector will hand over the discovered references to another thread which will eventually enqueue the references. The magic starts hereMadsen

© 2022 - 2024 — McMap. All rights reserved.