You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何添加System.out.println后Java程序不再抛出OutOfMemoryError?

为什么添加System.out.println(myList)后不再抛出OutOfMemoryError?

这个现象的核心和Java的垃圾回收时机、内存分配节奏以及ArrayList的扩容机制紧密相关,我们一步步拆解原因:

1. 先搞懂prog1抛出OOM的本质

在prog1的无限循环里,代码不断向ArrayList添加字符串常量"My String":

public static void main(String[] args) { 
    List<String> myList = new ArrayList<>(); 
    try { 
        while (true) { 
            myList.add("My String"); 
        } 
    } catch (Exception e) { 
        e.printStackTrace(); 
    } 
}
  • 首先要纠正一个误区:"My String"是字符串字面量,会被存入JVM的字符串常量池,所有add操作都是添加同一个对象的引用,真正吃内存的是ArrayList内部的elementData数组(用来存储这些引用)。
  • ArrayList的扩容逻辑是:当数组容量不够时,会创建一个新的更大的数组(默认是原容量的1.5倍),把旧数组的元素复制过去后就丢弃旧数组。
  • 问题出在循环速度太快:ArrayList扩容的速度远远超过JVM垃圾回收的速度——旧的elementData数组还没来得及被GC回收,新的数组又被创建出来,堆内存被大量待回收的旧数组和新数组占满,最终触发OutOfMemoryError。

2. prog2不抛出OOM的关键变化

添加System.out.println(myList)后,循环的执行节奏被彻底改变:

public static void main(String[] args) { 
    List<String> myList = new ArrayList<>(); 
    try { 
        while (true) { 
            myList.add("My String"); 
            System.out.println(myList); 
        } 
    } catch (Exception e) { 
        e.printStackTrace(); 
    } 
}

这里有两个核心影响:

  • 循环速度骤降:System.out.println(myList)会调用ArrayList.toString(),遍历所有元素生成一个超长字符串,再执行控制台IO输出——IO操作的速度比内存操作慢几个数量级,每次循环的耗时从微秒级拉长到毫秒甚至更久。
  • 垃圾回收被及时触发:每次println生成的超长字符串是临时对象,执行完输出后就没有任何引用指向它,会变成垃圾。同时,因为循环变慢了,JVM有足够的时间在ArrayList需要扩容前触发GC:不仅能回收这些临时的超长字符串,还能及时清理ArrayList扩容产生的旧elementData数组。
  • 最终内存达到动态平衡:每次扩容需要的内存,都能通过GC回收足够的空间,不会出现内存耗尽的情况,自然也就不会抛出OutOfMemoryError。

内容的提问来源于stack exchange,提问作者Ravi K M

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.06 13:12:49