当前位置: 首页 > news >正文

《CopyOnWriteArrayList / CopyOnWriteArraySet 源码与“大对象复制”事故实录》

关键词:CopyOnWriteArrayList、CopyOnWriteArraySet、写时复制、大对象复制、内存爆炸、源码、面试、线上事故
适合人群:Java 初中高级工程师 · 面试冲刺 · 代码调优 · 架构设计
阅读时长:35 min(≈ 5500 字)
版本环境:JDK 17(源码行号对应 jdk-17+35)


1. 开场白:面试三连击,能抗算我输

  1. “CopyOnWriteArrayList 新增一个元素到底几次内存拷贝?”
  2. “为什么迭代器没有 fail-fast?却可能读到‘过期’数据?”
  3. “100 MB 大对象 list 一次 add 会怎样?YoungGC 从 30 ms → 800 ms 你慌不慌?”

阿里 P8 面完 100 人,能把“写时复制、volatile 数组、快照迭代、大对象放大比”说透的不超过 5 个。
线上事故:某推荐系统用 CopyOnWriteArrayList<byte[]> 缓存 200 MB 图片 batch,大促期间每秒 200 次模型热更新,触发 200 * 200 MB = 40 GB/s 复制流量,机器直接卡死,FullGC 每 5 秒一次,回滚包车。
背完本篇,你能精确到源码行号解释“array = Arrays.copyOf(array, len + 1)”放大效应,并给出 3 种替代方案,让面试官心服口服。


2. 知识骨架:COW 家族一张图

CopyOnWriteArrayList
├─final transient ReentrantLock lock = new ReentrantLock();
├─private transient volatile Object[] array;
└─All write operations -> lock + Arrays.copyOf()CopyOnWriteArraySet
├─底层 CopyOnWriteArrayList<E> al;
└─add() 直接调用 al.addIfAbsent()
特性CopyOnWriteArrayListArrayList
线程安全是(写锁)
读操作无锁,volatile 数组无锁
迭代器快照,无 fail-fast快速失败
写代价O(n) 复制 + 1 次新增O(1)
适用场景读多写少、迭代频繁读写均衡

3. 身世档案:核心字段一表打尽

字段含义备注
arrayvolatile Object[]快照底层
lockReentrantLock写锁
addIfAbsent去重逻辑Set 复用

4. 原理解码:源码逐行,行号指路

4.1 写时复制核心:add(E e)(行号 394)

public boolean add(E e) {final ReentrantLock lock = this.lock;lock.lock();                                    // ① 全局写锁try {Object[] elements = getArray();int len = elements.length;Object[] newElements = Arrays.copyOf(elements, len + 1); // ② 全量复制newElements[len] = e;                       // ③ 新增元素setArray(newElements);                      // ④ volatile 写回return true;} finally {lock.unlock();}
}

每写一次 = 数组长度 * 引用字节数 的内存拷贝;大对象 = 放大器。

4.2 读操作:无锁快照(行号 335)

public E get(int index) {return get(getArray(), index);                  // 直接读 volatile 数组
}
final Object[] getArray() {return array;
}

无锁、弱一致性;可能读到“稍早”快照,但不抛异常。

4.3 迭代器:快照式(行号 914)

public Iterator<E> iterator() {return new COWIterator<E>(getArray(), 0);       // 拿到当前数组引用
}
static final class COWIterator<E> implements ListIterator<E> {private final Object[] snapshot;private int cursor;COWIterator(Object[] elements, int initialCursor) {cursor = initialCursor;snapshot = elements;                            // 快照,后续写不影响}
}

遍历全程无锁、无 fail-fast,但内存占用 = 快照时刻数组大小。

4.4 Set 去重:addIfAbsent(行号 424)

public boolean addIfAbsent(E e) {Object[] snapshot = getArray();return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :addIfAbsent(e, snapshot);
}
private boolean addIfAbsent(E e, Object[] snapshot) {final ReentrantLock lock = this.lock;lock.lock();try {Object[] current = getArray();int len = current.length;if (snapshot != current) {                      // ⑤ 再次检查// 重新比对}Object[] newElements = Arrays.copyOf(current, len + 1);newElements[len] = e;setArray(newElements);return true;} finally {lock.unlock();}
}

快照 + 双检锁,保证线程安全去重。


5. 实战复现:3 段代码 + GC 压测

5.1 大对象复制放大比

int batchSize = 1000;
int objSize = 100 * 1024;          // 100 KB
CopyOnWriteArrayList<byte[]> list = new CopyOnWriteArrayList<>();
byte[] big = new byte[objSize];// JProfiler 观测
for (int i = 0; i < batchSize; i++) {list.add(big);                  // 每次复制 1000 * 100 KB ≈ 100 MB
}

结果:

  • 复制峰值 200 MB(老数组 + 新数组并存)
  • YoungGC 从 30 ms → 800 ms

5.2 读性能对比

List<String> cow = new CopyOnWriteArrayList<>();
List<String> syn = Collections.synchronizedList(new ArrayList<>());
// 16 线程读 1M 次
runRead(cow); // 平均 12 ms
runRead(syn); // 平均 95 ms(读也加锁)

COW 读无锁,完胜。

5.3 迭代器快照内存占用

CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>();
Collections.addAll(list, new Integer[1_000_000]);
// 拿到迭代器后写 10 次
Iterator<Integer> it = list.iterator();
for (int i = 0; i < 10; i++) list.add(i);
// 迭代器仍遍历 100 万元素,内存占用 100 万引用

6. 线上事故:200 MB 图片 batch 复制风暴

背景
推荐系统缓存 CopyOnWriteArrayList<byte[]> batch,每批 200 MB,模型热更新 200 次/秒。

现象
机器内存从 8 GB 涨到 40 GB,FullGC 每 5 秒一次,CPU 100%。

根因
写时复制 = 200 MB × 2(老 + 新)× 200 次 = 80 GB/s 瞬时流量。

复盘

  1. 压测复现:复制放大 2 倍内存峰值。
  2. 修复:换成 ConcurrentLinkedQueue<byte[]> + 读写分离,内存平稳。
  3. 防呆:
    • 元素 > 10 KB 禁用 COW;
    • 静态代码检查:COW 泛型参数大小阈值告警。

7. 面试 10 连击:答案 + 行号

问题答案
1. 写时复制过程?lock → Arrays.copyOf → 新数组 + 1 元素 → volatile 写回(行号 394)
2. 读操作为什么无锁?直接读 volatile 数组(行号 335)
3. 迭代器是 fail-fast 吗?否,快照无并发异常(行号 914)
4. 能放 null 吗?可以,但 Set 去重需 equals 判断
5. 适用场景?读多写少,元素小
6. 大对象后果?复制放大 2 倍内存,GC 抖动
7. Set 如何去重?addIfAbsent + 双检锁(行号 424)
8. 计数器单元?无,直接 size() 读数组长度
9. 如何降低复制?批量 addAll、使用不可变列表
10. 现代替代方案?ConcurrentLinkedQueueImmutableList + RWLock

8. 总结升华:一张脑图 + 三句话口诀

[脑图文字版]
中央:CopyOnWriteArrayList
├─写:lock + copy
├─读:volatile 无锁
├─迭代:快照
└─大对象:复制放大

口诀:
“写锁复制读无锁,迭代快照不抛错;大对象一入内存炸,换成队列解厄祸。”


9. 下篇预告

阶段 3 继续深潜《BlockingQueue 家族源码:ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue 实现差异》将带你手写一把锁、双锁、零传递,敬请期待!


10. 互动专区

你在生产环境踩过 COW 复制放大坑吗?评论区贴出 GC 图 / 堆 Dump,一起源码级排查!

http://www.dtcms.com/a/500020.html

相关文章:

  • 做网站的品牌公司有哪些安康市110报警平台
  • 三水住房和城乡建设局的网站一键生成logo免费图
  • 自用EUBIU
  • 省住房城乡建设厅网站保险查询平台
  • 智能PDU在数据中心场景中的应用与解决方案
  • 网站登录界面图片用什么软件做深圳关键词优化报价
  • 中信建设证券官方网站佛山网页设计怎么做
  • Tomcat 类加载器隔离机制的实际应用
  • 咨询网站 模板国泰君安官方网站建设集团
  • Go基础知识(一)
  • 网站开发c外贸企业邮箱哪个好用
  • 鸿蒙Next振动开发指南:打造沉浸式触觉反馈体验
  • 网站美工外包公司改号宝网站搭建
  • h5游戏免费下载:滑雪大挑战
  • 高端制作网站哪家专业湖北建设工程注册中心网站
  • 包管理 pip ,conda;pycharm中使用conda 创建的虚拟环境
  • wordpress 域名使用网站内容优化细节
  • K8s Ingress 详解与部署实战
  • 一般网站开发的硬件要求使用flash做网站
  • 制作网站开发wordpress彻底禁用google
  • tauri + rust的环境搭建---初始化以及构建
  • 哪个网站可以做制图兼职嘉兴企业网站制作
  • 3.2队列
  • Particles Color and Depth Textures
  • 关键词搜不到我的网站竞价排名点击
  • Kolmogorov-Smirnov检验:从理论到实践的全解读
  • 怎么用wordpress建电商网站吗wordpress钩子函数
  • 临沂免费做网站网站服务器要求
  • 【STM32F1标准库】代码——SPI通信
  • 丽水品牌网站设计做网站商家