arraylist内存占用高源于未预估容量导致频繁扩容、冗余空间及大对象引用;应调用ensurecapacity预分配容量,避免copyonwritearraylist滥用,并及时清理大对象引用。

ArrayList 本身不“内存过大”,问题出在未合理预估容量导致频繁扩容,每次扩容都要新建数组、复制旧数据,产生大量短命对象,加剧 GC 压力,间接表现为内存占用高、响应变慢。
明确 size 和 capacity 的区别
size 是当前存了多少个元素,capacity 是底层数组实际有多长。二者常不相等——比如 add 了 100 个元素后,capacity 可能已是 144(10 → 15 → 22 → 33 → 49 → 73 → 109 → 144)。这些多出来的空位占着内存,又没被利用。
- 用反射或调试器看
elementData.length,就能确认真实 capacity - 调用
list.size()只告诉你用了多少,不是占了多少 - 大批量写入前不干预,JDK 默认的 1.5 倍扩容策略会不断制造冗余空间
用 ensureCapacity 预分配足够容量
这是最直接有效的手段。在批量添加前,根据业务数据规模一次性把底层数组拉到够用大小,避免中间所有扩容动作。
- 例如:已知要塞 8 万条日志,就写
list.ensureCapacity(80000) - 即使初始是空 ArrayList,ensureCapacity 也会立即创建指定长度的数组(跳过懒加载)
- 注意不是构造时传参——
new ArrayList(80000)效果相同,但 ensureCapacity 更灵活(比如集合复用场景)
避免存储大对象引用而不释放
ArrayList 存的是引用,真正吃内存的是它指向的对象。如果 list 里塞的是 10MB 的 byte[] 或大 POJO,即使 size=1,内存压力也很大。
- 检查元素类型:是否无意中缓存了未压缩的图片、原始文件流、全量 JSON 字符串等
- 及时清理不再需要的引用:
list.clear()或list = null,帮助 GC 回收底层大对象 - 考虑用更轻量结构替代:如只存 ID 或摘要,需要时再查详情
读多写少且需线程安全?慎选 CopyOnWriteArrayList
它的设计就是“写一次,拷全量”,每次 add/remove 都复制整个数组。如果原 list 已有数万元素,一次写操作就会瞬间多占一份内存,极易触发 Young GC 甚至 Full GC。
- 除非明确满足“读远大于写 + 元素量小 + 不允许读阻塞”三个条件,否则别默认选它
- 替代方案:读写分离 +
Collections.synchronizedList,或用ConcurrentHashMap模拟索引 - 若已用 CopyOnWriteArrayList 且出现内存飙升,优先排查写频次和单次写入的数据量











