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());
}
}
UPD:
我尝试在jСonsole和jcmd中执行GC但输出没有改变。
答案 0 :(得分:5)
WeakHashMap
包含使用Map.Entry
引用密钥的WeakReference
个实例(实际上,在OpenJDK / Oracle JDK中,it directly extends WeakReference
)。
当GC发生时,现在引用缺席键的条目不会从地图中神奇地删除:它们在被清除之前仍然存在,这就是为什么该值仍然存在并且尚未被收集的原因。
在OpenJDK中,使用ReferenceQueue
在expungeStaleEntries()
中发生,并且从多个地方调用该方法:
size()
resize()
getTable()
本身是通过多种方法调用的,包括get()
和put()
如果您希望自己的价值可以收集垃圾,则应与WeakHashMap
进行互动,例如:通过询问size()
或进行查找。
请注意,这意味着在第二次垃圾回收之前无法收集该值。
如果我没记错的话,它在番石榴中的工作方式大致相同。
答案 1 :(得分:2)
在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
。
答案 2 :(得分:1)
您无法控制java中的GC。 VM决定。我从未遇到需要System.gc()的情况。由于System.gc()调用简单地称为VM执行垃圾收集并且它还执行完全垃圾收集(多代堆中的新旧代),因此它实际上可以导致消耗更多的cpu周期。必要的。
在jdk 1.7以后,您可以使用以下命令强制gc运行
jcmd <pid> GC.run
使用jps