arraylist扩容核心是arrays.copyof引发的o(n)数组拷贝,触发于add前size+1>capacity,首次容量为10,后续按oldcapacity+(oldcapacity>>1)即1.5倍增长,预设容量可避免频繁复制带来的内存与gc压力。

监控 ArrayList 动态扩容的内存开销,核心是抓住“数组复制”和“容量跃变”这两个关键行为——因为扩容本身不产生新对象引用,但 Arrays.copyOf 会分配新数组并拷贝旧数据,这才是真实内存压力来源。
看扩容发生时机:从 size 和 capacity 的差值入手
ArrayList 没有公开的 capacity() 方法,但你可以通过反射或估算间接判断是否即将扩容:
- 每次
add()前,检查list.size() == list.toArray().length(低效但直观);更合理的是维护一个已知初始容量的列表,用size()推算当前是否临近满载 - 若使用无参构造,首次
add后容量为 10;后续扩容点依次是:10 → 15 → 22 → 33 → 49 → 73 → 109 …(按 ×1.5 向上取整) - 记录每次
size()达到这些阈值的时刻,就能定位扩容发生时间点
捕获实际内存分配:用 JVM 工具观测数组创建行为
扩容本质是创建更大 Object[] 并丢弃旧数组。可通过以下方式观测:
Java开发手册规约集合,基于阿里巴巴Java开发手册(嵩山版)。 涵盖7大维度:编程规约、异常日志、单元测试、安全规约、MySQL数据库、工程结构、设计规约。 当用户需要:(1) 编写或审查Java代码 (2) 检查命名/代码规范 (3) 处理异常和日志 (4) 编写单元测试 (5) 安全编码 (6) 数据库设...
-
jstat -gc
:关注 YGC次数和EU(Eden 使用量)突增,频繁扩容会导致 Eden 区快速填满、触发 Minor GC -
jmap -histo:live
:执行前后对比,查找 [Ljava.lang.Object;实例数与总大小是否明显增长 -
Java Flight Recorder(JFR):开启
jdk.ObjectAllocationInNewTLAB和jdk.ObjectAllocationOutsideTLAB事件,筛选大数组(如 >8KB)的分配堆栈,直接定位Arrays.copyOf调用位置
主动控制与量化开销:预分配 + 手动统计
最实用的方式不是“被动监控”,而是“提前约束+事后核算”:
- 用
new ArrayList(expectedSize)或list.ensureCapacity(expectedSize)避免扩容,再对比两种写法下Runtime.getRuntime().totalMemory() - freeMemory()的差值 - 写个简单计数器:继承 ArrayList,重写
grow()(需用反射绕过 private),在复制前打点日志:log.info("grow from {} to {}", oldCapacity, newCapacity),再乘以4/8 字节×长度估算本次分配字节数 - 注意:一次扩容复制 N 个元素,就产生约
N × 4 字节(对象引用)的新内存需求,加上旧数组待回收,瞬时堆压力可达两倍
识别异常模式:什么情况说明扩容成了瓶颈
不是所有扩容都危险,但出现以下信号就要警惕:
- 同一 ArrayList 在短时间内(如 1 秒内)扩容 ≥3 次,通常意味着初始容量严重低估
- JFR 中看到大量
Arrays.copyOf出现在热点方法栈顶,且参数newLength增长缓慢(如 10→15→22),说明是小步慢扩,累计开销高 - GC 日志中出现
Allocation Failure频率升高,且每次 YGC 后老年代占用稳步上升——说明短生命周期的大数组被晋升了
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










