WeakHashMap 在完整 GC 期间是否被清除?

发布于 2024-12-25 21:21:08 字数 972 浏览 2 评论 0原文

我在使用 Wea​​kHashMap 时遇到了一些麻烦。

考虑这个示例代码:

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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

夜夜流光相皎洁 2025-01-01 21:21:08

即时编译器分析代码,发现循环后没有使用 anObjectanOtherObject ,并将它们从局部变量表中删除或将它们设置为 < code>null,而循环仍在运行。这称为 OSR 编译。

随后 GC 会收集这些字符串,因为不再保留对它们的强引用。

如果您在循环后使用 anObject,您仍然会收到 OutOfMemoryError

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

The just-in-time compiler analyzes the code, sees that anObject and anOtherObject are not used after the loop, and removes them from the local variable table or sets them to null, 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 an OutOfMemoryError.

Update: You'll find a more detailed discussion about OSR compilation in my blog.

最终幸福 2025-01-01 21:21:08

深入挖掘后发现,JLS 第 12.6.1 节明确涵盖了这一点:

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

(粗体是我添加的内容) .)

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");

上面是一个更简单的示例,显示对象首先被最终确定并被 GC 处理,即使对 thing 的引用仍然存在(这里被打印,然后是 bam。)

Bit of digging reveals that this is explicitly covered in the JLS, section 12.6.1:

Optimizing transformations of a program can be designed that reduce the number of objects that are reachable to be less than those which would naively be considered reachable. For example, a compiler or code generator may choose to set a variable or parameter that will no longer be used to null to cause the storage for such an object to be potentially reclaimable sooner.

(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:

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");

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.)

手长情犹 2025-01-01 21:21:08

只是为 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

最后一个编译(带有 @ 标志)是 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:

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

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文