我的理解是,在下一个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?
答案 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.copyOf
或WeakReference
时发生
如果您希望列表实际释放空间以响应内存压力,则需要检测并从列表中删除损坏的Calendar
对象。这不是微不足道的。
您可以在WeakReference
中对每个WeakReference
进行排队,并让队列处理器删除每个ReferenceQueue
。但是,由于列表删除是O(N),因此效率很低。
另一个想法是让队列处理器增加一个私有计数器,让WeakReference
方法使用计数器来决定它应该扫描和删除损坏的引用。
您还应该考虑调用add
...或等效的...来减少后备存储大小。但请记住,这会暂时增加内存利用率。
这可能需要自定义ArrayList::trimToSize
实施。
答案 1 :(得分:0)
List和WeakReference都可以在该代码中自行引发OOM,即使GC收集了所有Calendar实例。