关于ArrayList的subList方法,还会出现另外一个问题就是强引用释放问题。看如下案例:
public static void main(String[] args) {
List<List<Integer>> data = new ArrayList<>();
for(int i=0;i<100000;i++) {
List<Integer> list = IntStream.rangeClosed(1,100000).boxed().collect(Collectors.toList());
data.add(list.subList(0,10));
}
}
在设置jvm参数为 -Xms100M -Xmx100M的情况下,运行一段时间之后:
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
at java.lang.Integer.valueOf(Integer.java:832)
at java.util.stream.IntPipeline$$Lambda$1/990368553.apply(Unknown Source)
at java.util.stream.IntPipeline$4$1.accept(IntPipeline.java:250)
at java.util.stream.Streams$RangeIntSpliterator.forEachRemaining(Streams.java:110)
at java.util.Spliterator$OfInt.forEachRemaining(Spliterator.java:693)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:482)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472)
at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
at com.dhb.ArrayList.test.SubListOOM.main(SubListOOM.java:15)
没错,会出现OOM问题。实际上在读过ArrayList源码分析(基于jdk1.8)(二):subList陷阱之后,就能想到这个问题。这也是最近在阅读极客时间的时候,正好也发现了这个问题就一并总结。
前文提到,如果采用subList则实际上是返回的一个内部类SubList。
subList方法 :
public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);
return new SubList(this, 0, fromIndex, toIndex);
}
SubList源码:
private class SubList extends AbstractList<E> implements RandomAccess {
private final AbstractList<E> parent;
private final int parentOffset;
private final int offset;
int size;
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方法调用的时候将this传递到了SubList的parent属性中。这是一个强引用。那么在GC的过程中,这是不会被回收的。如果我们在实际的编码过程中对SubList不了解,误将subList当成了一个全新的ArrayList,这就有可能因为GC无法回收之前的原始List而导致OOM异常。
结合前文的知识,我们只需要稍加改动,虽然执行时间很长,但是就就不会出现OOM了。
public static void main(String[] args) {
List<List<Integer>> data = new ArrayList<>();
for(int i=0;i<100000;i++) {
List<Integer> list = IntStream.rangeClosed(1,100000).boxed().collect(Collectors.toList());
data.add(new ArrayList<>(list.subList(0,10)));
}
}
因此copyOnRead或CopyOnWrite这是一个非常重要的设计模式,在后面会专门进行分析。