完整GC期间是否清除了WeakHashMap?

时间:2012-01-11 11:25:30

标签: java jit weak-references

我遇到了WeakHashMap的一些麻烦。

请考虑以下示例代码:

List<byte[]> list = new ArrayList<byte[]>();

Map<String, Calendar> map = new WeakHashMap<String, Calendar>();
String anObject = new String("string 1");
String anOtherObject = new String("string 2");

map.put(anObject, Calendar.getInstance());
map.put(anOtherObject, Calendar.getInstance());
// In order to test if the weakHashMap works, i remove the StrongReference in this object
anObject = null;
int i = 0;
while (map.size() == 2) {
   byte[] tab = new byte[10000];
   System.out.println("iteration " + i++ + "map size :" + map.size());
   list.add(tab);
}
System.out.println("Map size " + map.size());

此代码有效。在循环内部,我正在创建对象。当发生次要GC时,映射大小在第1360次迭代时等于1。一切都好。

现在我评论这一行:

//anObject = null; 

我希望有一个OutOfMemoryError,因为mapSize总是等于2.但是在第26XXX次迭代时,会发生一个完整的GC并且地图大小等于0.我不明白为什么?

我认为地图不应该已经清除,因为还有对两个对象的强引用。

3 个答案:

答案 0 :(得分:10)

即时编译器分析代码,看到循环后没有使用anObjectanOtherObject,并将它们从局部变量表中删除或将它们设置为{{1} ,循环仍然在运行。这称为OSR编译。

以后GC会收集字符串,因为没有对它们的强引用。

如果您在循环后使用null,则仍会获得anObject

更新:我会在博客中找到有关OSR compilation的更详细讨论。

答案 1 :(得分:7)

挖掘的内容表明,JLS第12.6.1节明确涵盖了这一点:

  

可以设计优化程序的转换,以减少可达到的对象数量,使其少于可以被认为可达的对象数量。例如,编译器或代码生成器可以选择设置一个不再用于null的变量或参数,以使此类对象的存储可能更快地回收。

(Bolding是我的补充。)

http://java.sun.com/docs/books/jls/third_edition/html/execution.html#12.6.1

所以从本质上讲,JIT可以随时删除强引用,如果它能够解决它们永远不会被再次使用的话 - 这正是这里发生的事情。

这是一个很好的问题,并且可以很容易地显示一个非常好的益智游戏,因为一个对象似乎在范围上有很强的引用,并不一定意味着它没有被垃圾收集。继此之后,这意味着您明确无法保证终结器何时运行,甚至可能在对象仍然在范围内的情况下!

例如:

List<byte[]> list = new ArrayList<byte[]>();

Object thing = new Object() {
    protected void finalize() {
        System.out.println("here");
    }
};
WeakReference<Object> ref = new WeakReference<Object>(thing);

while(ref.get()!=null) {
    list.add(new byte[10000]);
}
System.out.println("bam");

以上是一个更简单的示例,即使对thing的引用仍然存在(此处打印,然后是bam),也会首先显示对象已完成并首先进行GC。

答案 2 :(得分:7)

只是为 Joni Salonen berry120 的优秀答案添加一些东西。可以证明,JIT实际上负责“变量删除”,只需将其与-Djava.compiler=NONE关闭即可。一旦你把它关掉,你就会得到OOME。

如果我们想知道幕后发生了什么,选项XX:+PrintCompilation会显示JIT活动。将它与问题中的代码一起使用我们得到的输出如下:

1       java.lang.String::hashCode (64 bytes)
2       java.lang.String::charAt (33 bytes)
3       java.lang.String::indexOf (151 bytes)
4       java.util.ArrayList::add (29 bytes)
5       java.util.ArrayList::ensureCapacity (58 bytes)
6  !    java.lang.ref.ReferenceQueue::poll (28 bytes)
7       java.util.WeakHashMap::expungeStaleEntries (125 bytes)
8       java.util.WeakHashMap::size (18 bytes)
1%      WeakHM::main @ 63 (126 bytes)
Map size 0

最后一次编译(使用@标志)是OSR(On Stack Replacement)编译(有关详细信息,请查看https://gist.github.com/rednaxelafx/1165804#osr)。简单来说,它使VM能够在运行时替换方法,并用于提高陷入循环的Java方法的性能。我猜想在触发编译后,JIT会删除不再使用的变量。