怎么分析 CopyOnWriteArrayList 在写操作时通过数组拷贝实现最终一致性的性能代价

落杰姑娘_4173

落杰姑娘_4173

2026-04-16

166人浏览

原创

copyonwritearraylist 的写操作因每次均需 o(n) 数组拷贝且在 reentrantlock 临界区内执行,导致高并发下线程卡顿;同时引发内存翻倍、gc 频繁及迭代器持有旧数组致内存泄漏等问题。

怎么分析 copyonwritearraylist 在写操作时通过数组拷贝实现最终一致性的性能代价

写操作触发的数组拷贝到底花了多少时间

每次 add、remove 或 set 都会调用 Arrays.copyOf,复制整个底层数组。这个操作的时间复杂度是 O(n),不是常数——n 是当前数组长度,不是要加的那 1 个元素。10 万条数据时加一个元素,就要 memcpy 10 万次引用;100 万条就是百万级拷贝。

更关键的是:它发生在持有 ReentrantLock 的临界区内,所有写线程串行排队。高并发写入时,你看到的不是“慢”,而是“卡住”——线程在锁上等待,而前面那个线程还在 memcpy。

  • 用 JFR(Java Flight Recorder)抓取 java.lang.System::arraycopy 的调用栈,能直接定位哪次 add 引发了长耗时
  • 避免在循环里调用 add:比如 list.addAll(collection) 本质是 N 次 add,不是批量优化
  • JDK 8+ 的 CopyOnWriteArrayList 没有 addAll 的原子批量实现,别被名字骗了

内存占用翻倍不是错觉,是必然发生的事

CopyOnWriteArrayList 的数组引用是 volatile 的,但旧数组不会立刻被回收。新数组分配 + 旧数组待 GC,瞬时堆内存占用就是 2× 当前数据量。如果单个元素是 1KB 的对象,10 万条就多占 100MB 堆空间。

这会直接触发老年代 GC 频率上升,尤其在 CMS 或 ZGC 低延迟场景下,容易出现“写一次,停顿 200ms”的现象。

  • 用 jstat -gc <pid></pid> 观察 OU(Old Used)和 YGCT(Young GC Time)是否随写操作陡增
  • 监控 Object[] 实例数:频繁写入后,堆里会堆积大量生命周期短的数组实例
  • 别依赖 System.gc() 强制回收——它不保证立即释放旧数组,还可能干扰 JVM 自身 GC 节奏

迭代器快照导致的“看不见”的内存泄漏

你调用 iterator() 得到的不是实时视图,而是当时数组的一个快照引用。只要这个迭代器没被回收,它持有的旧数组就无法被 GC。

典型陷阱:事件分发中用 for (Listener l : listeners),每个 l.onEvent() 又启一个异步任务,而任务执行周期远长于写操作间隔——结果是每次写都留下一批“僵尸数组”,堆内存缓慢爬升。

  • 用 MAT(Eclipse Memory Analyzer)分析堆转储,筛选 Object[] 的 GC Roots,常能看到大量 CopyOnWriteArrayList$COWIterator
  • 不要在 long-running 线程中长期持有迭代器;若必须遍历,考虑改用 toArray() 获取副本后尽快释放引用
  • CopyOnWriteArrayList 不支持 removeIf 这类修改迭代器状态的操作——它会抛 UnsupportedOperationException

为什么“最终一致性”在配置中心里反而成了坑

所谓最终一致,是指写操作完成后,后续读操作“ eventually ”能看到新值。但这个“eventually”没有 SLA:它取决于线程调度、GC 停顿、甚至 CPU 缓存同步延迟。在集群配置中心里,不同节点看到的配置版本可能差好几秒。

更麻烦的是:它不提供版本号或更新戳,你没法判断“我读到的是不是最新版”。灰度发布时,部分节点加载了新配置,部分还在用旧配置,问题难以复现。

  • 别把它当配置存储主干;更适合只读缓存层(如监听器列表),且写入频率 ≤ 1 次/秒
  • 若真要用作配置容器,必须配合外部版本控制(如用 AtomicLong version 手动管理)
  • getArray() 返回的是原始数组引用,不是副本——误用它做“手动快照”会导致并发修改风险

真正难处理的从来不是“怎么写”,而是“谁在读、读多久、读完之后还在不在用”。数组拷贝只是表象,背后是内存生命周期、GC 行为、线程调度三者的耦合。一旦迭代器持有时间不可控,或者写入节奏和 GC 周期共振,性能断崖就不是理论风险了。

数码产品性能查询
数码产品性能查询

该软件包括了市面上所有手机CPU,手机跑分情况,电脑CPU,电脑产品信息等等,方便需要大家查阅数码产品最新情况,了解产品特性,能够进行对比选择最具性价比的商品。

下载

相关标签:

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
java
java

Java是一个通用术语,用于表示Java软件及其组件,包括“Java运行时环境 (JRE)”、“Java虚拟机 (JVM)”以及“插件”。php中文网还为大家带了Java相关下载资源、相关课程以及相关文章等内容,供大家免费下载使用。

2023.06.15

10157

6

java正则表达式语法
java正则表达式语法

java正则表达式语法是一种模式匹配工具,它非常有用,可以在处理文本和字符串时快速地查找、替换、验证和提取特定的模式和数据。本专题提供java正则表达式语法的相关文章、下载和专题,供大家免费下载体验。

2023.07.05

7302

9

java自学难吗
java自学难吗

Java自学并不难。Java语言相对于其他一些编程语言而言,有着较为简洁和易读的语法,本专题为大家提供java自学难吗相关的文章,大家可以免费体验。

2023.07.31

6412

8

java配置jdk环境变量
java配置jdk环境变量

Java是一种广泛使用的高级编程语言,用于开发各种类型的应用程序。为了能够在计算机上正确运行和编译Java代码,需要正确配置Java Development Kit(JDK)环境变量。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

2023.08.01

1104

3

java保留两位小数
java保留两位小数

Java是一种广泛应用于编程领域的高级编程语言。在Java中,保留两位小数是指在进行数值计算或输出时,限制小数部分只有两位有效数字,并将多余的位数进行四舍五入或截取。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

2023.08.02

908

3

java基本数据类型
java基本数据类型

java基本数据类型有:1、byte;2、short;3、int;4、long;5、float;6、double;7、char;8、boolean。本专题为大家提供java基本数据类型的相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.02

1336

5

java有什么用
java有什么用

java可以开发应用程序、移动应用、Web应用、企业级应用、嵌入式系统等方面。本专题为大家提供java有什么用的相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.02

2669

5

java在线网站
java在线网站

Java在线网站是指提供Java编程学习、实践和交流平台的网络服务。近年来,随着Java语言在软件开发领域的广泛应用,越来越多的人对Java编程感兴趣,并希望能够通过在线网站来学习和提高自己的Java编程技能。php中文网给大家带来了相关的视频、教程以及文章,欢迎大家前来学习阅读和下载。

2023.08.03

19991

3

配置java环境变量
配置java环境变量

配置Java环境变量是为了让操作系统能够识别和使用Java的相关命令和功能。本专题为大家提供配置java环境变量相关文章,帮助大家解决问题。

2023.08.03

1195

8

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
热门推荐
/
最新课程
phpStudy极速入门视频教程
phpStudy极速入门视频教程

共6课时 | 54.6万人学习

独孤九贱(4)_PHP视频教程
独孤九贱(4)_PHP视频教程

共89课时 | 133.4万人学习