为何添加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
相关产品推荐
相关产品推荐

