【问题标题】:Are WeakHashMap cleared during a full GC?完整 GC 期间是否清除了 WeakHashMap?
【发布时间】:2012-01-11 11:25:30
【问题描述】:

我在使用 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。我不明白为什么?

我认为地图不应该被清除,因为这两个对象也有强引用。

【问题讨论】:

  • 我认为你的测试不正确。如果你把while (map.size() == 2) {改成while (map.size() &gt; 0) {,不管你是否评论anObject = null,这两个测试都会结束,直到map为空。顺便说一句,我已经试过了。
  • 在末尾打印anObjectanOtherObject。编译器看到您不再使用它们,可以提前删除它们。

标签: java jit weak-references


【解决方案1】:

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

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

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

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

【讨论】:

  • 我认为你是完全正确的 - 但这不是潜在的破坏 JIT 优化吗?如果 anObject 有一个终结器,并且在该引用消失之前它已被 GC 处理,则终结器可能会在它原本应该执行之前执行。
  • 它会破坏什么?当终结器运行时,强引用不再存在。
  • 它可能会中断,因为它可以比您预期的更快地运行终结器;在硬引用实际超出范围之前。
  • 好点。我想教训是你不能以任何方式依赖于何时(或if)终结器运行。它可能比您预期的要早,而不仅仅是晚。
【解决方案2】:

一点点挖掘表明,这在 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。)

【讨论】:

    【解决方案3】:

    只是为 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会移除不再使用的变量。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-03-18
      • 2021-07-08
      • 2018-04-09
      • 2019-08-06
      • 2020-04-05
      • 2015-11-23
      • 1970-01-01
      • 2023-03-23
      相关资源
      最近更新 更多