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

Sla*_*ade 11 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());
Run Code Online (Sandbox Code Playgroud)

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

现在,当我评论这一行:

//anObject = null; 
Run Code Online (Sandbox Code Playgroud)

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

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

Jon*_*oni 10

即时编译器分析代码,在循环之后查看anObject并且anOtherObject不使用它们null,并在循环仍在运行时将它们从局部变量表中删除或将它们设置为.这称为OSR编译.

后来GC收集字符串,因为没有对它们的强引用.

如果你anObject在循环之后使用,你仍然会得到一个OutOfMemoryError.

更新:您将在我的博客中找到有关OSR编译的更详细讨论.


Mic*_*rry 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");
Run Code Online (Sandbox Code Playgroud)

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


jal*_*aba 7

只是为Joni Salonenberry120的优秀答案添加一些东西.可以证明,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
Run Code Online (Sandbox Code Playgroud)

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