WeakHashMap 是在不断增长,还是会清除垃圾键?
Is WeakHashMap ever-growing, or does it clear out the garbage keys?
我正在尝试使用 WeakHashMap
as a concurrent Set
的弱引用。
this.subscribers =
Collections.synchronizedSet(
Collections.newSetFromMap(
new WeakHashMap <>()
)
);
当一个元素进入垃圾回收时,我的集合继续将其报告为回收的一部分。所以地图似乎在不断增长。
文档说:
When a key has been discarded its entry is effectively removed from the map,…
但实际情况似乎并非如此。
有没有 WeakHashMap
清除碎屑的点?
是的,键在垃圾被实际收集后被清除
是的,WeakHashMap
确实清除了碎屑。 已进入垃圾收集的键不再在大小中报告。但是您必须等待垃圾收集真正发生。
您关于要进行垃圾回收的对象的说法很可能是错误的。也许您的对象成为垃圾收集的候选对象,但尚未被收集。尝试调用垃圾收集器并等待它完成。但请记住,对 System.gc()
的调用只是对 JVM 的建议,可能会被忽略,具体取决于您的 JVM 实现和当前的运行时场景。
这是一个完整的示例应用程序。请注意,无论是调用 Set::remove
还是让对象超出范围,Set
都会报告 size
的减少。
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() );
}
}
}
看到这个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
在我的例子中,使用 Java 10.0.2 version of OpenJDK-based Zulu JVM from Azul Systems,垃圾收集器似乎在我的请求下激活。如果我将延迟一秒钟或 System.gc
调用注释掉,那么最后报告的大小仍然是 3
而不是预期的 2
.
您甚至可以在 running this code live at IdeOne.com 时看到此行为。请注意下面的最后一项是 3
而上面的是 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
当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
仍然对该值有强引用。实际清理发生在以下时间:
- 垃圾收集器已将事件发布到引用队列(您无法控制何时发生)。
- 您可以调用
WeakHashMap
上的任何其他方法 - 这将执行所需的清理工作。
这里有一个例子来说明发生了什么。
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; }
}
}
我正在尝试使用 WeakHashMap
as a concurrent Set
的弱引用。
this.subscribers =
Collections.synchronizedSet(
Collections.newSetFromMap(
new WeakHashMap <>()
)
);
当一个元素进入垃圾回收时,我的集合继续将其报告为回收的一部分。所以地图似乎在不断增长。
文档说:
When a key has been discarded its entry is effectively removed from the map,…
但实际情况似乎并非如此。
有没有 WeakHashMap
清除碎屑的点?
是的,键在垃圾被实际收集后被清除
是的,WeakHashMap
确实清除了碎屑。 已进入垃圾收集的键不再在大小中报告。但是您必须等待垃圾收集真正发生。
您关于要进行垃圾回收的对象的说法很可能是错误的。也许您的对象成为垃圾收集的候选对象,但尚未被收集。尝试调用垃圾收集器并等待它完成。但请记住,对 System.gc()
的调用只是对 JVM 的建议,可能会被忽略,具体取决于您的 JVM 实现和当前的运行时场景。
这是一个完整的示例应用程序。请注意,无论是调用 Set::remove
还是让对象超出范围,Set
都会报告 size
的减少。
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() );
}
}
}
看到这个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
在我的例子中,使用 Java 10.0.2 version of OpenJDK-based Zulu JVM from Azul Systems,垃圾收集器似乎在我的请求下激活。如果我将延迟一秒钟或 System.gc
调用注释掉,那么最后报告的大小仍然是 3
而不是预期的 2
.
您甚至可以在 running this code live at IdeOne.com 时看到此行为。请注意下面的最后一项是 3
而上面的是 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
当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
仍然对该值有强引用。实际清理发生在以下时间:
- 垃圾收集器已将事件发布到引用队列(您无法控制何时发生)。
- 您可以调用
WeakHashMap
上的任何其他方法 - 这将执行所需的清理工作。
这里有一个例子来说明发生了什么。
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; }
}
}