Java-ArrayList-subList()方法不恰当使用引起的OutOfMemoryError

本文探讨了Java中使用ArrayList的subList方法可能导致的内存泄漏问题。通过详细解析subList的实现原理,揭示了它如何保留对原始列表的强引用,从而阻止垃圾回收并最终导致OutOfMemoryError。文章提供了修改代码的解决方案,建议使用新的集合来存储子列表,避免内存泄漏。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

先看看代码,逻辑很简单:
1.创建了一个ArrayList,然后往这个list里面放了一些数据,得到了一个size=100000的list;
2. 从这个list取出一个size=1的sublist;
3.将sublist保存到内存中;
4.原有的list数据被抛弃;
代码:

package com.tsaoko.sched.service.task;

import java.util.ArrayList;
import java.util.List;

/**
 * @author Walker
 * @since 1.0 2018-10-17
 */
public class SublistTest {

    public static void main(String[] args) {
        List<List<Integer>> cache = new ArrayList<>();

        try {
            while (true) {
                List<Integer> list = new ArrayList<>();
                for (int j = 0; j < 100000; j++) {
                    list.add(j);
                }

            List<Integer> sublist = list.subList(0, 1);
            cache.add(sublist);
        }
    } finally {
            System.out.println("cache size = " + cache.size());
        }
    }

}

正常情况下,按照设想保存在内存中size的大小应该很小,然而,运行一段时间后,程序报OutOfMemoryError错误:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
cache size = 820
	at java.util.Arrays.copyOf(Arrays.java:3181)
	at java.util.ArrayList.grow(ArrayList.java:265)
	at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:239)
	at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:231)
	at java.util.ArrayList.add(ArrayList.java:462)
	at com.tsaoko.sched.service.task.SublistTest.main(SublistTest.java:19)	
	

看到这里,先看看list.subList(0, 1)方法源码:

public List<E> subList(int fromIndex, int toIndex) {
     subListRangeCheck(fromIndex, toIndex, size);
     return new SubList(this, 0, fromIndex, toIndex);
 }

SubList是ArrayList类的内部类,看看其构造方法:

 SubList(AbstractList<E> parent,
         int offset, int fromIndex, int toIndex) {
     this.parent = parent;
     this.parentOffset = fromIndex;
     this.offset = offset + fromIndex;
     this.size = toIndex - fromIndex;
     this.modCount = ArrayList.this.modCount;
 }

可以看出SubList原理:
1.保存父ArrayList的引用;
2.通过计算offset和size表示subList在原始list的范围;
由此可知,这种方式的subList保存对原始list的引用,而且是强引用,导致GC不能回收,故而导致内存泄漏,当程序运行一段时间后,程序无法再申请内存,抛出内存溢出错误。
解决这个问题可以采用将subList后的结果保存到新集合中,修改后代码:

package com.tsaoko.sched.service.task;

import java.util.ArrayList;
import java.util.List;

/**
 * @author Walker
 * @since 1.0 2018-10-17
 */
public class SublistTest {

    public static void main(String[] args) {
        List<List<Integer>> cache = new ArrayList<>();

        try {
            while (true) {
                List<Integer> list = new ArrayList<>();
                for (int j = 0; j < 100000; j++) {
                    list.add(j);
                }
            //采用新集合保存子集合
            List<Integer> sublist = new ArrayList<>(list.subList(0, 1));
            cache.add(sublist);
        }
    } finally {
            System.out.println("cache size = " + cache.size());
        }
    }

}

最后,看看String的substring(int beginIndex)源码,1.8版本:

    public String substring(int beginIndex) {
        if (beginIndex < 0) {
            throw new StringIndexOutOfBoundsException(beginIndex);
        }
        int subLen = value.length - beginIndex;
        if (subLen < 0) {
            throw new StringIndexOutOfBoundsException(subLen);
        }
        return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
    }

在1.7之前版本substring存在同样类似问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值