WeakHashMap 在完整 GC 期间是否被清除?
我在使用 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。我不明白为什么?
我认为地图不应该被清除,因为也有对这两个对象的强引用。
I encountered some troubles with WeakHashMap.
Consider this sample code:
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());
This code works. Inside the loops, i'm creating object.When a minor GC occurs, the map size is equal to 1 at the 1360th iteration. All is OK.
Now when i comment this line:
//anObject = null;
I expect to have an OutOfMemoryError because the mapSize is always equal to 2. However at the 26XXX th iteration, a full GC occurs and the map size is equal to 0. I dont understand why?
I thought that the map shouldn't have cleared because there are also strong references to both objects.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
即时编译器分析代码,发现循环后没有使用
anObject
和anOtherObject
,并将它们从局部变量表中删除或将它们设置为 < code>null,而循环仍在运行。这称为 OSR 编译。随后 GC 会收集这些字符串,因为不再保留对它们的强引用。
如果您在循环后使用
anObject
,您仍然会收到OutOfMemoryError
。更新:您将找到有关的更详细讨论OSR 编译 在我的博客中。
The just-in-time compiler analyzes the code, sees that
anObject
andanOtherObject
are not used after the loop, and removes them from the local variable table or sets them tonull
, while the loop is still running. This is called OSR compilation.Later the GC collects the strings because no strong references to them remain.
If you used
anObject
after the loop you'd still get anOutOfMemoryError
.Update: You'll find a more detailed discussion about OSR compilation in my blog.
深入挖掘后发现,JLS 第 12.6.1 节明确涵盖了这一点:
(粗体是我添加的内容) .)
http://java.sun.com/docs/books/jls/third_edition/html/execution.html#12.6.1
因此,本质上,JIT 可以在任何时候删除强引用它希望是否能够解决它们将不再被使用的问题——这正是这里发生的情况。
这是一个很好的问题,并且是一个很好的谜题,可以很容易地表明,仅仅因为一个对象似乎在范围内具有强引用,并不一定意味着它没有被垃圾收集。接下来,这意味着您明确无法保证终结器何时运行,甚至可能是在对象似乎仍在范围内的情况下!
例如:
上面是一个更简单的示例,显示对象首先被最终确定并被 GC 处理,即使对
thing
的引用仍然存在(这里被打印,然后是 bam。)Bit of digging reveals that this is explicitly covered in the JLS, section 12.6.1:
(Bolding is my addition.)
http://java.sun.com/docs/books/jls/third_edition/html/execution.html#12.6.1
So in essence, the JIT is allowed to remove strong references whenever it wants if it can work out that they'll never be used again - which is exactly what's happening here.
This is a great question though and makes for a great puzzler that can easily show just because an object appears to have a strong reference in scope, doesn't necessarily mean it hasn't been garbage collected. Following on from this it means you explicitly can't guarantee anything about when a finalizer will run, this may even be in the case where it seems like the object is still in scope!
Eg:
The above is a simpler example that shows the object gets finalized and GC'd first even though the reference to
thing
still exists (here is printed, then bam.)只是为 Joni Salonen 和 berry120 的出色答案添加一点内容。可以看出,JIT 实际上负责“变量删除”,只需使用
-Djava.compiler=NONE
将其关闭即可。一旦你把它关掉,你就会得到 OOME。如果我们想知道幕后发生了什么,选项 XX:+PrintCompilation 会显示 JIT 活动。将其与问题中的代码一起使用,我们得到的输出如下:
最后一个编译(带有 @ 标志)是 OSR(堆栈替换)编译(检查 https://gist.github.com/rednaxelafx/1165804#osr 了解更多详情)。简而言之,它使VM能够在运行时替换方法,并用于提高陷入循环的Java方法的性能。我猜测触发此编译后,JIT 会删除不再使用的变量。
Just to add a little thing to the excellent answers from Joni Salonen and berry120. It can be shown that the JIT is actually the responsible for the "variable removing" simply turning it off with
-Djava.compiler=NONE
. Once you turn it off, you get the OOME.If we want to know what is happening under the hoods, the option
XX:+PrintCompilation
shows the JIT activity. Using it with the code from the question the output we get is the following:The last compilation (with the @ flag) is a OSR (On Stack Replacement) compilation (check https://gist.github.com/rednaxelafx/1165804#osr for further details). In simple words, it enables the VM to replace a method while it is running and it is used to improve performance of Java methods stuck in loops. I would guess that after this compilation is triggered, the JIT removes the variables that are no longer used.