CopyOnWriteArrayList内存占用过多

在这里插入图片描述

大家好,我是哪吒。

一、CopyOnWriteArrayList

CopyOnWriteArrayList是Java中的一个线程安全的ArrayList,它在每次修改时都会创建一个新的数组,并将原数组的引用赋值给新数组,从而达到线程安全的目的。由于它不断地创建新的数组并保留旧数组,因此内存占用可能会比较多。

CopyOnWriteArrayList具有以下特点:

  1. 线程安全:CopyOnWriteArrayList使用读写锁来确保并发访问时的线程安全性,因此可以在多线程环境下使用。
  2. 内存占用较高:由于它在每次修改操作时都会创建一个新的数组,并且会保留旧数组,因此内存占用可能会比较多。
  3. 适合读密集型应用:由于它只会在写操作时进行数组复制,因此对于读密集型的应用,使用CopyOnWriteArrayList可以减少锁竞争,提高并发性能。
  4. 不支持写操作:CopyOnWriteArrayList不支持add()和remove()方法,因为这些操作需要修改底层数组,从而导致所有线程都看到不一致的结果。因此,如果需要执行写操作,可以考虑使用其他的线程安全的数据结构,如Collections.synchronizedList()。

总之,CopyOnWriteArrayList适合读密集型应用,并且在写操作时需要注意内存占用问题。如果需要执行写操作,可以考虑使用其他的线程安全的数据结构。

二、CopyOnWriteArrayList的适用场景

CopyOnWriteArrayList主要适用于读多写少的场景,例如配置信息、缓存等数据的读取和更新操作。由于写操作的时候会进行数组复制,会消耗内存,如果原数组的内容比较多的情况下可能导致young gc或者full gc。因此,对于读操作非常频繁,而写操作相对较少的场景,使用CopyOnWriteArrayList可以提供较好的并发性能和线程安全性。

需要注意的是,CopyOnWriteArrayList在写操作中使用了ReentrantLock锁来保证线程安全,并替换原array属性;但是在读的时候直接读取array,可能会发生在写操作替换array前后,从而引发数据不一致的问题。因此,在使用CopyOnWriteArrayList时需要注意以下几点:

  1. 如果写操作未完成,那么直接读取原数组的数据是不一致的。
  2. 如果写操作完成,但是引用还未指向新数组,那么也是读取原数组数据是不一致的。
  3. 如果写操作完成,并且引用已经指向了新的数组,那么直接从新数组中读取数据是一致的。

总之,CopyOnWriteArrayList适用于读多写少且数据一致性要求较高的场景。

三、CopyOnWriteArrayList内存占用过多的解决方法

  1. 尽量避免在CopyOnWriteArrayList中进行频繁的修改操作。如果可能的话,可以尝试将这些修改操作集中在一起进行,从而减少数组的创建和销毁次数。
  2. 使用其他的线程安全的数据结构,比如Collections.synchronizedList()。这种数据结构使用的内存开销通常比CopyOnWriteArrayList小。
  3. 如果必须要使用CopyOnWriteArrayList,可以使用WeakReference来减少内存占用。具体来说,可以将数组元素使用WeakReference管理,当数组不再被引用时,垃圾回收器可以将其回收,从而释放内存。

另外,你还可以通过查看JVM的内存快照(比如使用jstat工具)来分析内存占用过多的原因。这可以帮助你更好地了解内存占用情况并采取相应的措施。

四、CopyOnWriteArrayList.add()源码分析

public boolean add(E e) {
	// 获取独占锁
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
    	// 获取array
        Object[] elements = getArray();
        int len = elements.length;
        // 复制array到新数组,添加元素到新数组
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        newElements[len] = e;
        // 替换数组
        setArray(newElements);
        return true;
    } finally {
	    // 释放锁
        lock.unlock();
    }
}

CopyOnWriteArrayList内部定义了一个内部array数组,然后复制array到新数组,添加元素到新数组。所有的读操作都是基于新的array对象进行的。

private transient volatile Object[] array;

final Object[] getArray() {
    return array;
}

因为上了独占锁ReentrantLock,如果多个线程调用add()方法,只会有一个线程会获得到该锁,其他线程被阻塞,直至锁被释放, 由于加了锁,所以整个操作的过程是原子性操作。

由于每次写入的时候都会对数组对象进行复制,复制过程不仅会占用双倍内存,还需要消耗 CPU 等资源,如果要保存大量元素,并放任其成长,内存和CPU将面临重大考验,场面堪比金角巨兽,后果不堪设想。

对 CopyOnWriteArrayList 每一次修改,都会重新创建一个大对象,并且原来的大对象也需要回收,这都可能会触发 GC,如果超过老年代的大小则容易触发Full GC,引起应用程序长时间停顿。

在这里插入图片描述

🏆哪吒多年工作总结:Java学习路线总结,搬砖工逆袭Java架构师

  • 10
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 10
    评论
CopyOnWriteArrayList是一个线程安全的List实现,它通过每次修改操作(添加、删除、修改)时都创建一个新的底层数组来实现线程安全性。 CopyOnWriteArrayList的特点如下: 1. 线程安全:多个线程可以同时读取CopyOnWriteArrayList的内容,而不需要额外的同步机制。这使得它非常适合在读操作远远多于写操作的场景中使用。 2. 写操作的代价较高:每次对CopyOnWriteArrayList进行写操作时,都会创建一个新的底层数组,因此写操作的代价较高。 3. 实时性较低:由于写操作会创建新的底层数组,读取操作可能会看到旧的数据,因此CopyOnWriteArrayList的实时性较低。 使用CopyOnWriteArrayList的示例代码如下: ```java import java.util.concurrent.CopyOnWriteArrayList; public class Main { public static void main(String[] args) { CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>(); list.add("Hello"); list.add("World"); for (String item : list) { System.out.println(item); } } } ``` 在上述代码中,我们创建了一个CopyOnWriteArrayList,并向其中添加了两个元素。然后使用增强for循环遍历CopyOnWriteArrayList中的元素,并打印输出。 需要注意的是,CopyOnWriteArrayList适用于读操作远远多于写操作的场景,如果写操作非常频繁,可能会导致性能问题。此外,CopyOnWriteArrayList不保证元素的顺序性,因为在写操作时会创建新的底层数组。
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

哪 吒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值