ArrayList采用1.5倍扩容而非2倍,是为了降低连续内存碎片率:2倍扩容(如100→200)导致闲置空间激增(如存120个元素浪费80位),加剧老年代碎片化和GC压力;1.5倍(100→150)闲置更少(仅浪费30位),内存利用率稳定在70%~85%,旧数组更易被完整回收。

1.5 倍不是为了内存对齐,而是为了压低连续内存的碎片率——2 倍扩容在 ArrayList 场景下会快速制造大量闲置空间,尤其在长期运行或内存受限环境中容易引发 GC 压力和 OOM。
为什么 2 倍扩容会让 ArrayList 更容易产生内存碎片
ArrayList 底层是 Object[],必须申请连续堆内存。2 倍扩容(如 10 → 20 → 40 → 80)导致每次扩容后,已存元素只占新数组约 50%:
- 容量从 100 扩到 200,但只存了 120 个元素 → 80 个位置空置
- 这些空置空间无法被其他对象复用,又不能被 GC 单独回收(整个数组对象还活着)
- 多次扩容后,堆中会堆积大量“半满”的大数组对象,加剧老年代碎片化
而 1.5 倍扩容(100 → 150 → 225)让闲置比例更平缓: - 100 扩到 150 后存 120 个元素 → 仅浪费 30 个位置 - 内存利用率始终维持在 70%~85% 区间,GC 回收旧数组时更容易识别为完整可回收块
1.5 倍和内存对齐其实无关,别被位运算误导
有人看到 oldCapacity + (oldCapacity >> 1) 就联想到“对齐”,这是误解:
- 该位运算是为了高效算出 ×1.5(向下取整),不是为了地址对齐
- Java 数组本身不强制按 2 的幂次方分配,JVM 堆分配器关注的是页对齐(通常 4KB/8KB),跟数组长度是否为 2 的幂无关
- Arrays.copyOf() 分配的新数组,其起始地址由 JVM 决定,和容量数值无直接关系
- 真正影响缓存局部性的,是数组内容是否连续、访问模式是否顺序——这点 1.5 倍和 2 倍没差别
什么时候 1.5 倍会被绕过?别指望它永远生效
扩容逻辑实际是“保底策略”,不是硬约束:
- 首次添加元素时,空 ArrayList 直接扩到 10,不走 1.5 倍计算
- addAll() 一次性加 500 个元素,当前容量是 300 → grow() 会跳过 1.5 倍,直接设新容量为 500
- 若 oldCapacity 接近 Integer.MAX_VALUE,oldCapacity + (oldCapacity >> 1) 可能溢出,此时退回到最大安全值
也就是说,1.5 倍只在“小步慢增”场景下稳定起效;一旦批量操作或初始预估失误,它就自动让位给实际需求。
真正容易被忽略的点是:扩容倍数的影响不在单次操作,而在长期累积——频繁的小扩容复制带来 CPU 开销,而一次性的大扩容则吃掉大量连续堆空间。选 1.5 倍,本质是在这两者之间卡住一个能跑得久、还不太占地方的平衡点。

















