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

先看看代码,逻辑很简单:
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存在同样类似问题。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: ArrayListJava中的一个类,它可以动态地存储一组元素,可以根据需要动态增加或减少元素的数量,是一种非常方便的数据结构。 ArrayList的基本用法包括创建ArrayList对象、添加元素、获取元素、修改元素、删除元素等。首先,可以使用如下代码创建一个空的ArrayList对象: ```java ArrayList<String> list = new ArrayList<String>(); ``` 上面代码创建了一个类型为String的ArrayList对象,该对象初始为空。然后,可以使用add()方法ArrayList中添加元素,例如: ```java list.add("apple"); list.add("banana"); list.add("orange"); ``` 上述代码向list中添加了三个字符串元素。可以使用get()方法获取ArrayList中的元素,例如: ```java String first = list.get(0); // 获取第一个元素 ``` 可以使用set()方法修改ArrayList中的元素,例如: ```java list.set(1, "pear"); // 将第2个元素改为"pear" ``` 可以使用remove()方法删除ArrayList中的元素,例如: ```java list.remove(2); // 删除第3个元素 ``` 以上就是ArrayList的基本用法。需要注意的是,ArrayList中的索引从0开始。 ### 回答2: ArrayListJava中非常常用的数据结构。它提供了一个可以动态添加、删除、修改的可变长度的序列。 使用ArrayList时,首先需要引入它的包:java.util。然后可以使用如下语法创建一个ArrayList对象: ```java ArrayList<String> list = new ArrayList<String>(); ``` 这里的`<String>`说明了这个ArrayList中的元素类型是String。当然,也可以使用其他类型作为元素类型。例如: ```java ArrayList<Integer> numbers = new ArrayList<Integer>(); ArrayList<Double> prices = new ArrayList<Double>(); ``` 可以使用`add()`方法来向ArrayList中添加元素: ```java list.add("apple"); list.add("orange"); list.add("banana"); ``` 可以使用`get()`方法来获取指定位置的元素: ```java String fruit = list.get(1); //获取第二个元素,即"orange" ``` 可以使用`size()`方法来获取ArrayList中元素的个数: ```java int size = list.size(); //获取ArrayList中元素的个数 ``` 可以使用`set()`方法来修改指定位置的元素: ```java list.set(1, "pear"); //将第二个元素修改为"pear" ``` 可以使用`remove()`方法来删除指定位置的元素: ```java list.remove(2); //删除第三个元素,即"banana" ``` 需要注意的是,ArrayList中的元素是有序的,且可以重复。因此,可以使用循环来遍历ArrayList中的元素: ```java for(int i=0; i<list.size(); i++){ String fruit = list.get(i); System.out.println(fruit); } ``` 或者使用增强型循环(foreach): ```java for(String fruit : list){ System.out.println(fruit); } ``` 总之,使用ArrayList可以方便地处理可变长度的序列。在实际开发中,它有着广泛的应用场景,例如处理文件或数据库中的数据,实现算法或数据结构等。 ### 回答3: ArrayListJava中一个非常常用的容器类。他的优点是可以存储任意类型的对象,可以动态扩容,因此在使用上非常的方便。 使用ArrayList需要在代码中首先调用import java.util.ArrayList进行导入,随后可以通过ArrayList<类型> name = new ArrayList<类型>()这个语句声明一个ArrayList,并将其命名为name,同时指定ArrayList中存储的对象类型为类型。当我们需要添加元素时,可以通过name.add(element)将元素添加到ArrayList中。我们也可以通过name.get(index)方法获取ArrayList中指定位置的元素,通过name.set(index,value)方法ArrayList中某个位置的元素替换为新的元素。同时,我们也可以调用name.size()方法获取ArrayList中元素的数量。 值得注意的是,ArrayList中的元素是以索引的方式存储的,这意味着我们可以根据元素的位置进行添加、修改、删除等操作。而且,由于ArrayList的容量是可变的,因此其内部必须动态地管理数据的内存,这会影响到ArrayList的性能。当然,这个影响是很小的,不会对代码的运行产生显著的影响。 总之,ArrayListJava中非常常用的容器类,其可以存储任意类型的对象,同时调用也非常方便。但在使用时需要注意其操作的复杂度,以及不能存储基本数据类型。如果需要在ArrayList中存储基本数据类型,需要借助Boxing和Unboxing机制将其转换为对应的包装类。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值