arraylist扩容采用1.5倍是内存占用、复制开销与插入性能的最优平衡点,既避免1.2倍导致的频繁扩容,又防止2倍造成空间浪费;公式为newcapacity = oldcapacity + (oldcapacity >> 1),首次扩容固定为10,大容量时防溢出,复制依赖优化的system.arraycopy。

ArrayList扩容为什么是1.5倍,不是2倍或1.2倍
因为1.5倍是在内存占用、复制开销、插入性能三者之间算出来的“甜点”——既避免频繁扩容(1.2倍太保守),又不浪费太多空间(2倍太激进)。JDK早期用过1.5倍,后来在Java 19+里悄悄改成 oldCapacity + (oldCapacity >> 1),本质还是1.5倍,只是位运算更高效。
常见错误现象:OutOfMemoryError: Java heap space 不一定是因为数据量大,而是连续小批量添加触发多次扩容,每次复制旧数组,GC压力陡增;反过来,如果手动预设过大初始容量(比如 new ArrayList(1000000)),又会立刻占满堆内存,导致其他对象分配失败。
- 实际扩容公式是:
newCapacity = oldCapacity + (oldCapacity >> 1)(即 ×1.5 向下取整) - 最小扩容步长固定为10:空
ArrayList第一次add()后容量变成10,不是1.5×0 - 当
oldCapacity很大(比如接近Integer.MAX_VALUE),1.5倍可能溢出,此时会退回到Integer.MAX_VALUE或抛OutOfMemoryError
扩容时数组复制的开销到底有多大
每次扩容都要调用 Arrays.copyOf(),底层是 System.arraycopy(),属于JVM内建优化操作,但依然要遍历、搬运所有元素。对百万级 String 对象,一次扩容可能卡住几十毫秒——尤其在响应敏感场景(如网关、实时计算)里,这已经算严重抖动。
使用场景差异明显:日志收集线程持续 add(),比批处理一次性 addAll() 更容易踩中扩容频次陷阱;而如果元素是 int 这种基本类型,复制成本低,1.5倍就显得更“划算”。
立即学习“Java免费学习笔记(深入)”;
-
System.arraycopy()是本地方法,不走Java栈,但仍有内存带宽压力 - 如果元素是引用类型,只复制引用(8字节),不是深拷贝对象本身
- 开启ZGC或Shenandoah时,大数组复制可能触发更多写屏障开销,需实测
怎么判断你的ArrayList是不是正在被扩容拖慢
不能只看 size(),得盯住 capacity——但Java没直接暴露它。得用反射或借助 ArrayList 的包私有字段 elementData,或者更稳妥地用 Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory() 配合 jstat 观察GC前后内存变化趋势。
常见错误现象:压测时吞吐量上不去,top 显示CPU不高,但 jstack 抓到大量线程卡在 ArrayList.add() 里的 Arrays.copyOf() 调用栈;或者 VisualVM 里看到 System.arraycopy 占用采样热点前3。
- 用
java -XX:+PrintGCDetails看是否伴随频繁 young GC(扩容后旧数组变垃圾) - 对关键列表,初始化时显式传入预估容量:
new ArrayList(expectedSize) - 如果无法预估,考虑用
LinkedList?别——它随机访问是O(n),除非你只做头尾增删
1.5倍在不同JDK版本里真的一致吗
逻辑一致,实现细节微调。Java 7 到 Java 18 都用 oldCapacity + (oldCapacity >> 1);Java 19 引入了 ArrayList.grow() 方法封装扩容逻辑,但公式没变;Java 21 的 SequencedCollection 接口扩展也不影响底层扩容策略。
真正影响兼容性的不是倍数,而是某些国产JVM(如毕昇JDK)或Android ART运行时对 ArrayList 做了定制优化,有的把首次扩容从10改成16,有的加了缓冲区预分配——这些不会改1.5倍逻辑,但会让容量增长曲线看起来“不一样”。
- 别依赖具体容量值做单元测试断言,比如
assertEquals(15, list.size())是错的,应测行为而非实现 - 自定义集合类继承
ArrayList时,重写ensureCapacity()可以干预扩容时机,但别改倍数逻辑,否则破坏JDK语义 - Android API 24+ 的
ArrayList和OpenJDK行为一致,但低版本(如API 16)有bug:扩容后modCount没更新,导致并发修改异常漏报
扩容因子看着是个数学常数,实际牵扯内存模型、GC策略、CPU缓存行对齐,甚至JIT编译器对 arraycopy 的内联决策。多数时候1.5倍够用,但只要你在做高频实时写入、堆内存紧张、或跑在非标准JVM上,就得亲手去量——而不是相信“默认最合理”。









