【问题标题】:If weakly reachable objects are GC'ed then why there is OOM error如果弱可达对象被 GC'ed 那么为什么会出现 OOM 错误
【发布时间】:2026-01-11 11:45:01
【问题描述】:

我的理解是,弱可达对象在下一个 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() 创建的日历对象将在每次 1000000 迭代后被 GC'ed因此,如果我在 1000000 迭代中没有收到 OOM 错误,那么我不应该在任何后续循环中得到它,因为所有以前的 Calender 对象都会在下一个循环中进行 GC?

【问题讨论】:

  • GC 不会中断正在运行的方法。
  • @TimothyTruckle 我在哪里说 GC 正在中断正在运行的方法?
  • 你的代码和你的语句一起...
  • @TimothyTruckle - 我不明白你想说什么,但是 GC 可以并且会中断正在运行的方法;见*.com/questions/19201332/java-gc-safepoint

标签: java memory-management memory-leaks


【解决方案1】:

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 ...,它指向作为 Reference 目标的对象。当Reference 被破坏时,会发生referent 设置为null,从而允许目标对象被GC(通常在下一个GC 循环中......但不一定)。但是Reference 对象本身并没有被释放......除非你让它无法访问。

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

最终,列表消耗的内存和它所持有的(损坏的)WeakReference 对象将填满堆,并且您会得到一个 OOME。 (它可能会发生在 Arrays.copyOf 调用中......但也可能发生在您分配 WeakReferenceCalendar 时)


如果您希望您的列表实际释放空间以响应内存压力,那么您将需要检测并从列表中删除损坏的WeakReference 对象。这不是小事。

  • 可以将每个WeakReference 加入ReferenceQueue 中,并让队列处理器删除每个损坏的WeakReference。但是,这将是低效的,因为列表删除是 O(N)。

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

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

这可能需要自定义 List 实现。

【讨论】:

    【解决方案2】:

    List 和 WeakReference 都可能在该代码中自行导致 OOM,即使所有 Calendar 实例都被 GC 收集。

    【讨论】:

    • 你的回答真的没用,能否请你再看一遍我的问题并详细说明一下?
    最近更新 更多