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