Java-ArrayList-subList()方法不恰當使用引起的OutOfMemoryError
阿新 • • 發佈:2018-12-15
先看看程式碼,邏輯很簡單: 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存在同樣類似問題。