1. 程式人生 > >Java-ArrayList-subList()方法不恰當使用引起的OutOfMemoryError

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存在同樣類似問題。