问题
I am trying to use WeakHashMap as a concurrent Set of weak references.
this.subscribers =
Collections.synchronizedSet(
Collections.newSetFromMap(
new WeakHashMap <>()
)
);
When an element goes to garbage-collection, my set continues to report it as a part of the collection. So it seems the map is ever-growing.
The documentation says:
When a key has been discarded its entry is effectively removed from the map,…
But that does not seem to be the case in practice.
Is there ever a point at which the WeakHashMap
clears out the detritus?
回答1:
Yes, keys cleared after garbage is actually collected
Yes, WeakHashMap
does clean out the detritus. The keys that have gone to garbage collection are no longer reported in the size. But you must wait for garbage-collection to actually take place.
Seems likely that you were incorrect about your objects going to garbage-collection. Perhaps your objects became candidates for garbage-collection, but have not yet been collected. Try invoking the garbage-collector and waiting a moment for it to complete. But remember, the call to System.gc()
is only a suggestion to the JVM and may be ignored depending on your JVM implementation and current runtime scenario.
Here is a complete example app. Notice that the Set
reports a decrease in size
whether calling Set::remove
or letting the object go out of scope.
package com.basilbourque.example;
import java.util.Collections;
import java.util.Set;
import java.util.UUID;
import java.util.WeakHashMap;
public class WeakHashMapExercise {
public static void main ( String[] args ) {
WeakHashMapExercise app = new WeakHashMapExercise();
app.doIt();
}
private void doIt ( ) {
Set < UUID > set =
Collections.synchronizedSet(
Collections.newSetFromMap(
new WeakHashMap <>()
)
);
UUID uuid1 = UUID.fromString( "a8ee1e34-cead-11e8-a8d5-f2801f1b9fd1" );
UUID uuid2 = UUID.fromString( "39bda2b4-5885-4f56-a900-411a49beebac" );
UUID uuid3 = UUID.fromString( "0b630385-0452-4b96-9238-20cdce37cf55" );
UUID uuid4 = UUID.fromString( "98d2bacf-3f7f-4ea0-9c17-c91f6702322c" );
System.out.println( "Size before adding: " + set.size() );
set.add( uuid1 );
set.add( uuid2 );
set.add( uuid3 );
set.add( uuid4 );
System.out.println( "Size after adding 4 items: " + set.size() ); // Expect 4.
set.remove( uuid3 );
System.out.println( "Size after removing item # 3: " + set.size() ); // Expect 3.
uuid2 = null; // Release that UUID to garbage-collection.
// That released object may still appear in our `Set` until garbage collection actually executes.
System.gc(); // Ask the JVM to run the garbage-collection. Only a suggestion, may be ignored.
try {
Thread.sleep( 1_000 ); // Wait a moment, just for the heck of it.
} catch ( InterruptedException e ) {
e.printStackTrace();
}
System.out.println( "Size after making garbage of item # 2: " + set.size() ); // Expect 2.
for ( UUID uuid : set ) {
System.out.println( uuid.toString() );
}
}
}
See this code run live at IdeOne.com.
Size before adding: 0
Size after adding 4 items: 4
Size after removing item # 3: 3
Size after making garbage of item # 2: 2
In my case, using Java 10.0.2 version of OpenJDK-based Zulu JVM from Azul Systems, the garbage collector does seem to be activating upon my request. If I comment out the delay for a second, or the System.gc
call, then the last size reported remains 3
rather than the expected 2
.
You can even see this behavior when running this code live at IdeOne.com. Notice how the last item below is 3
but above is 2
.
Size before adding: 0
Size after adding 4 items: 4
Size after removing item # 3: 3
Size after making garbage of item # 2: 3
回答2:
When garbage-collection clears a weak reference, it posts an "event" to a reference queue. That process is asynchronous, and even if GC has "cleared" your keys, WeakHashMap still has a strong reference to the value. The actual clean-up happens when:
- Garbage Collector has posted the event to the reference queue (you have no control of when this happens).
- You call any other method on the
WeakHashMap
- that will do the needed clean-up.
Here is an example to show what is going on.
public class WeakHashMapInAction {
public static void main(String[] args) {
Key key = new Key();
KeyMetadata keyMeta = new KeyMetadata("keyMeta");
WeakHashMap<Key, KeyMetadata> map = new WeakHashMap<>();
map.put(key, keyMeta);
// wrap the key into a weakReference
WeakReference<Key> keyReference = new WeakReference<>(key);
// force key to be GC-ed
key = null;
for (; keyReference.get() != null; ) {
System.gc();
}
// at this point keyReference::get returns null,
// meaning the GC has reclaimed "key";
// that does NOT mean WeakHashMap removed that entry though
// you can enable this code to see that "not yet collected" is not printed at all
// since you are giving enough time for the Reference thread to post to that ReferenceQueue
// LockSupport.parkNanos(10000000);
while (map.size() == 1) {
// if you run this enough times, you will see this sometimes is printed
// even if WeakHashMap::size calls "expungeStaleEntries" internally
// it does not mean that the event to the queue was pushed in time
// by the Reference thread
System.out.println("not yet collected");
}
System.out.println("collected");
}
static class Key {
}
@RequiredArgsConstructor
@Getter
static class KeyMetadata {
private final String someInfo;
// Constructor.
KeyMetadata ( String someInfo ) { this.someInfo = someInfo; }
}
}
来源:https://stackoverflow.com/questions/52789834/is-weakhashmap-ever-growing-or-does-it-clear-out-the-garbage-keys