如果可以访问弱可达对象,那么为什么会出现OOM错误

时间:2018-03-12 10:28:33

标签: java memory-management memory-leaks

我的理解是,在下一个GC周期中,一个弱可达的对象被垃圾收集,如果这是真的那么我永远不应该为下面的代码得到OutOfMemoryError,但是在运行10-15分钟后我得到OOM错误。

    List<Object> list = new ArrayList<>();
    while (true) {
        for(int i = 0; i < 1000000; i++){
            list.add(new WeakReference<Object>(Calendar.getInstance()));
        }
    }

有人可以解释为什么我仍然会获得OOM,我知道列表的大小会继续增加,但我的观点是,我使用Calendar.getInstance()创建的日历对象将在每次{{{{{{ 1}}迭代所以如果我在1000000迭代中没有得到OOM错误那么我不应该为任何后续循环得到它,因为所有以前的Calender对象都会在下一个循环中使用GC?

2 个答案:

答案 0 :(得分:3)

ArrayList类使用简单的策略来管理空间。当列表的后备数组已满并且您添加了另一个元素时,ArrayList会分配一个更大的数组并将元素从旧数组复制到新数组。新阵列的大小将比旧阵列大50%,直到阵列大小的架构限制。以下是Java 8实现:

     private void grow(int minCapacity) {
         // overflow-conscious code
         int oldCapacity = elementData.length;
         int newCapacity = oldCapacity + (oldCapacity >> 1);
         if (newCapacity - minCapacity < 0)
             newCapacity = minCapacity;
         if (newCapacity - MAX_ARRAY_SIZE > 0)
             newCapacity = hugeCapacity(minCapacity);
         // minCapacity is usually close to size, so this is a win:
         elementData = Arrays.copyOf(elementData, newCapacity);
     }

在您的示例中,您反复向列表中添加元素,这会导致列表的支持数组增长,增长和增长。

接下来,Reference是包含4个字段的类(在Java 8中),其中一个是referenent ...,它指向作为{{1}的目标的对象}。当Reference被破坏时,会发生Reference设置为referent,从而允许目标对象进行GC(通常在下一个GC循环中...但是不必要)。但null对象本身并未被释放......除非你让它无法访问。

在您的示例中,您没有做任何事情来使Reference对象无法访问。相反,他们正在列表中。

最终,列表所占用的内存和它所拥有的(损坏的)WeakReference对象将填满堆,然后你就得到了一个OOME。 (它可能会在WeakReference电话中发生......但也可能在您分配Arrays.copyOfWeakReference时发生

如果您希望列表实际释放空间以响应内存压力,则需要检测并从列表中删除损坏的Calendar对象。这不是微不足道的。

  • 可以WeakReference中对每个WeakReference进行排队,并让队列处理器删除每个ReferenceQueue。但是,由于列表删除是O(N),因此效率很低。

  • 另一个想法是让队列处理器增加一个私有计数器,让WeakReference方法使用计数器来决定它应该扫描和删除损坏的引用。

  • 您还应该考虑调用add ...或等效的...来减少后备存储大小。但请记住,这会暂时增加内存利用率。

这可能需要自定义ArrayList::trimToSize实施。

答案 1 :(得分:0)

List和WeakReference都可以在该代码中自行引发OOM,即使GC收集了所有Calendar实例。