When will Java WeakHashMap clean null key?
Asked Answered
B

4

3

In the code below nameRef.get() is null , after name = null and System.gc().

import java.lang.ref.WeakReference;

public class Main {

    public static void main(String[] args) {
        String name = new String("ltt");

        WeakReference<String> nameRef = new WeakReference<>(name);    
        System.out.println(nameRef.get()); // ltt

        name = null;
        System.gc();

        System.out.println(nameRef.get());  // null
    }    
}

WeakHashMap is based on WeakReference. At last, I think map.size() will be 0. In fact, it's 1.

import java.util.WeakHashMap;

public class Main2 {

    public static void main(String[] args) {
        String name = new String("ltt");
        WeakHashMap<String, Integer> map = new WeakHashMap<>();
        map.put(name, 18);
        System.out.println(map.size()); // 1

        name = null;
        System.gc();

        System.out.println(map.size());  // it's 1. why not 0 ?
    }    
}

When will Java WeakHashMap clean the null key?

Broderick answered 2/3, 2019 at 13:42 Comment(2)
It's a common misconception, but System.gc() does not force a garbage collection to occur.Tuscarora
Possible duplicate of When does System.gc() do anythingVulgate
A
9

The simple answer: you don't know.

Meaning: you have no control when the jvm kicks in and triggers a GC cycle. You have no control when eligible objects are actually collected.

Thus there is no way for you to know when the "state" of that map changes.

And yes, the other part is that calling System.gc() is only a recommendation to the jvm to do garbage collection. In a standard setup you have zero control if jvm follows that request, or ignores it. You would need to use very special JVMs for example to change that.

Typically, gc runs only take place when the jvm considers them necessary. So your map might keep its size at 1 as long as there is no shortage of memory. And as the answer by Holger nicely outlines even the GC running doesn't force the map to update that part.

Anabolism answered 2/3, 2019 at 13:50 Comment(0)
M
7

As pointed out by others, there is no guaranty that System.gc() performs an actual garbage collection. Neither is a garbage collection cycle guaranteed to actually collect a particular unreachable object.

But in your specific setup, it seems that System.gc() is sufficient to collect the object, as determined by your WeakReference example. But clearing a weak reference is not the same as enqueuing the reference, to allow the WeakHashMap to perform its cleanup. As explained in this answer, the cleanup of the WeakHashMap relies on the key reference to be enqueued, which will be checked each time you’re invoking a method on it, which is the subsequent map.size() invocation in your example.

As the documentation of WeakReference specifies:

Suppose that the garbage collector determines at a certain point in time that an object is weakly reachable. At that time it will atomically clear all weak references to that object and all weak references to any other weakly-reachable objects from which that object is reachable through a chain of strong and soft references. At the same time it will declare all of the formerly weakly-reachable objects to be finalizable. At the same time or at some later time it will enqueue those newly-cleared weak references that are registered with reference queues.

Note how clearing happens “at that time” whereas enqueuing will happen “at the same time or at some later time”.

In case of HotSpot JVM/OpenJDK, enqueuing happens asynchronously after the garbage collection and your main thread seems to run too fast, so the key reference has not been enqueued yet.

Inserting a small pause may cause the example program to succeed in typical environments:

import java.util.WeakHashMap;

public class Main2 {

    public static void main(String[] args) throws InterruptedException{
        String name = new String("ltt");
        WeakHashMap<String, Integer> map = new WeakHashMap<>();
        map.put(name, 18);
        System.out.println(map.size()); // 1

        name = null;
        System.gc();

        Thread.sleep(10);

        System.out.println(map.size()); // 0
    }    
}

Of course, this doesn’t change the fact that this is neat for testing, but not a guaranteed behavior, so you shouldn’t write production code relying on it.

Mcreynolds answered 5/3, 2019 at 10:50 Comment(0)
I
3

System.gc() only suggests a garbage collection. There are no guarantees made on when and even if the collection will happen. You are seeing exactly that, you made the suggestion but it was not done yet.

See 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. [...]

Having said that, there is no way for you to know when the entry in the map is gone, except checking it regularly.

Imperfect answered 2/3, 2019 at 13:50 Comment(2)
"there is no way for you to know when the entry in the map is gone". This is not entirely true. One can always provide a custom "expungeStaleEntries" method via Java Agent.Anyaanyah
@Anyaanyah Good point. But probably beyond the scope of what OP wants. If you are familiar with it, you could create an answer showing how that works :)Imperfect
A
0

Your demo has some problems. In fact, the size is 0 after System.gc(). In you situation is because when print the map's size GC has not finished yet, so the result is 1

Agapanthus answered 4/4, 2019 at 3:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.