为什么 WeakHashMap 在 GC 后对值有强引用?
Why WeakHashMap holds strong reference to value after GC?
WeakHashMap 中的关键对象变得弱可达。并且地图应该在GC之后删除条目。但是对值对象的强引用仍然存在。为什么?
使用番石榴弱键映射观察到相同的行为。
预期输出:
...
refKey.get = null
refValue.get = null
但我得到输出:
map.keys = []
map.values = []
map.size = 0
refKey.get = null
refValue.get = (123)
代码:
import java.lang.ref.WeakReference;
import java.util.Map;
import java.util.WeakHashMap;
import com.google.common.collect.MapMaker;
public class Test {
static class Number {
final int number;
public Number(int number) { this.number = number; }
public String toString() { return "(" + number + ")"; }
}
static class Key extends Number {
public Key(int number) { super(number); }
}
static class Value extends Number {
public Value(int number) { super(number); }
}
public static void main(String args[]) {
//Map<Key, Value> map = new MapMaker().weakKeys().makeMap();
Map<Key, Value> map = new WeakHashMap<>();
Key key = new Key(1);
Value value = new Value(123);
map.put(key, value);
WeakReference<Key> refKey = new WeakReference<>(key);
WeakReference<Value> refValue = new WeakReference<>(value);
key = null;
value = null;
System.gc();
System.out.println("map.keys = " + map.keySet());
System.out.println("map.values = " + map.values());
System.out.println("map.size = " + map.size());
System.out.println("refKey.get = " + refKey.get());
System.out.println("refValue.get = " + refValue.get());
}
}
更新:
我尝试在 jСonsole 和 jcmd 中执行 GC,但输出没有改变。
您无法控制 java 中的 GC。虚拟机决定。我从来没有 运行 遇到需要 System.gc() 的情况。由于 System.gc() 调用只是建议 VM 执行垃圾收集并且它还执行完整垃圾收集(multi-generational 堆中的旧代和新代),那么它实际上会导致更多 cpu 比必要消耗的周期。
在 jdk 1.7 之后你可以使用下面的命令强制 gc 到 运行
jcmd <pid> GC.run
使用 jps
获取进程 ID
运行 此代码修改和 Jconsole 中的强制 GC 确认删除了引用。
System.gc();
try {
while (refValue.get() != null) {
System.out.println("map.keys = " + map.keySet());
Thread.sleep(5000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("map.keys = " + map.keySet());
System.out.println("map.values = " + map.values());
System.out.println("map.size = " + map.size());
System.out.println("refKey.get = " + refKey.get());
System.out.println("refValue.get = " + refValue.get());
但是由于某些原因,如果在循环中不查询 map.keySet()
,它永远不会终止,即 refValue
不会变成 null
。
WeakHashMap
包含 Map.Entry
个使用 WeakReference
引用密钥的实例(实际上,在 OpenJDK / Oracle JDK、it directly extends WeakReference
).
当 GC 发生时,现在引用不存在键的条目不会神奇地从映射中删除:它们仍然存在直到被清除,这就是为什么值也仍然存在并且尚未被收集的原因。
在 OpenJDK 中,这发生在 expungeStaleEntries()
中使用 ReferenceQueue
,并且从许多地方调用了该方法:
size()
resize()
getTable()
本身由多个方法调用,包括 get()
和 put()
如果您希望您的值可以被垃圾回收,您应该与 WeakHashMap
交互,例如通过询问其 size()
或进行查找。
请注意,这意味着在 第二次 垃圾收集之前无法收集该值。
如果我没记错的话,它在 Guava 中的工作方式大致相同。
WeakHashMap 中的关键对象变得弱可达。并且地图应该在GC之后删除条目。但是对值对象的强引用仍然存在。为什么?
使用番石榴弱键映射观察到相同的行为。
预期输出:
...
refKey.get = null
refValue.get = null
但我得到输出:
map.keys = []
map.values = []
map.size = 0
refKey.get = null
refValue.get = (123)
代码:
import java.lang.ref.WeakReference;
import java.util.Map;
import java.util.WeakHashMap;
import com.google.common.collect.MapMaker;
public class Test {
static class Number {
final int number;
public Number(int number) { this.number = number; }
public String toString() { return "(" + number + ")"; }
}
static class Key extends Number {
public Key(int number) { super(number); }
}
static class Value extends Number {
public Value(int number) { super(number); }
}
public static void main(String args[]) {
//Map<Key, Value> map = new MapMaker().weakKeys().makeMap();
Map<Key, Value> map = new WeakHashMap<>();
Key key = new Key(1);
Value value = new Value(123);
map.put(key, value);
WeakReference<Key> refKey = new WeakReference<>(key);
WeakReference<Value> refValue = new WeakReference<>(value);
key = null;
value = null;
System.gc();
System.out.println("map.keys = " + map.keySet());
System.out.println("map.values = " + map.values());
System.out.println("map.size = " + map.size());
System.out.println("refKey.get = " + refKey.get());
System.out.println("refValue.get = " + refValue.get());
}
}
更新:
我尝试在 jСonsole 和 jcmd 中执行 GC,但输出没有改变。
您无法控制 java 中的 GC。虚拟机决定。我从来没有 运行 遇到需要 System.gc() 的情况。由于 System.gc() 调用只是建议 VM 执行垃圾收集并且它还执行完整垃圾收集(multi-generational 堆中的旧代和新代),那么它实际上会导致更多 cpu 比必要消耗的周期。
在 jdk 1.7 之后你可以使用下面的命令强制 gc 到 运行
jcmd <pid> GC.run
使用 jps
运行 此代码修改和 Jconsole 中的强制 GC 确认删除了引用。
System.gc();
try {
while (refValue.get() != null) {
System.out.println("map.keys = " + map.keySet());
Thread.sleep(5000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("map.keys = " + map.keySet());
System.out.println("map.values = " + map.values());
System.out.println("map.size = " + map.size());
System.out.println("refKey.get = " + refKey.get());
System.out.println("refValue.get = " + refValue.get());
但是由于某些原因,如果在循环中不查询 map.keySet()
,它永远不会终止,即 refValue
不会变成 null
。
WeakHashMap
包含 Map.Entry
个使用 WeakReference
引用密钥的实例(实际上,在 OpenJDK / Oracle JDK、it directly extends WeakReference
).
当 GC 发生时,现在引用不存在键的条目不会神奇地从映射中删除:它们仍然存在直到被清除,这就是为什么值也仍然存在并且尚未被收集的原因。
在 OpenJDK 中,这发生在 expungeStaleEntries()
中使用 ReferenceQueue
,并且从许多地方调用了该方法:
size()
resize()
getTable()
本身由多个方法调用,包括get()
和put()
如果您希望您的值可以被垃圾回收,您应该与 WeakHashMap
交互,例如通过询问其 size()
或进行查找。
请注意,这意味着在 第二次 垃圾收集之前无法收集该值。
如果我没记错的话,它在 Guava 中的工作方式大致相同。