First, let's look at an example
First define a list for storing Integer list, and cycle 1000 times. Each cycle will produce a list with a size of 1 million. Use subList() to obtain a list containing only one number and store it in data. At this time, what should the data in data look like? 1000 lists with only one number? Let's run it.
public class SubListDemo { private static List<List<Integer>> data = new ArrayList<>(); public static void oom() { for (int i = 0; i < 1000; i++) { List<Integer> rawList = IntStream.rangeClosed(1, 1000000).boxed().collect(Collectors.toList()); data.add(rawList.subList(0, 1)); } System.out.println(data); } public static void main(String[] args) { oom(); } }
Operation results

Ah, he reported a mistake. Why? Why not 1000 lists containing only one number?
analysis
Lists with a size of 10 million generated in these 1000 cycles are always strongly referenced by the list returned by subList(), so that they cannot be recycled. Next, let's see why the returned sub list will strongly refer to the original list.
We click to enter ArrayList Take a look at the source code of sublist() (partial source code interception)
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { transient Object[] elementData; private int size; private void add(E e, Object[] elementData, int s) { if (s == elementData.length) elementData = grow(); elementData[s] = e; size = s + 1; } public boolean add(E e) { modCount++; add(e, elementData, size); return true; } public List<E> subList(int fromIndex, int toIndex) { subListRangeCheck(fromIndex, toIndex, size); return new SubList<>(this, fromIndex, toIndex); } private static class SubList<E> extends AbstractList<E> implements RandomAccess { private final ArrayList<E> root; private final SubList<E> parent; private final int offset; private int size; public SubList(ArrayList<E> root, int fromIndex, int toIndex) { this.root = root; this.parent = null; this.offset = fromIndex; this.size = toIndex - fromIndex; this.modCount = root.modCount; } public boolean contains(Object o) { return indexOf(o) >= 0; } public Iterator<E> iterator() { return listIterator(); } public ListIterator<E> listIterator(int index) { checkForComodification(); rangeCheckForAdd(index); ... } private void checkForComodification() { if (root.modCount != modCount) throw new ConcurrentModificationException(); } } }
Let's look directly at the subList() method, where we will find:
First: subList() does not return an ArrayList, it returns an ArrayList
SubList class, and this is passed in during initialization.
Second: SubList is an internal class of ArrayList. If you look at his construction method again, you will find that his root is the original List, and the intercepted elements are not copied to the new variables during initialization. It can be seen that SubList is the view of the original List, not a new List. The modifications of the elements in the collection by both parties will affect each other. And because SubList has strong reference to the original List, these original collections cannot be garbage collected, resulting in OOM.
Third: we will find this in the construction method of SubList modCount = root. modCount; The modcount of SubList is the modcount of the original set. Modcount is a field maintained in ArrayList, indicating the number of structural modifications of the collection. Therefore, for the add and remove operation of the original set, the value of the original set modcount will be changed, while the modcount of the List obtained after subList() will not be changed.
validate
Next, we use another example to verify whether the SubList is the view of the original List, whether the modifications of the elements in the set by both parties will affect each other, and what are the changes of the modCount of the SubList and the original set.
public static void subListTest() { List<Integer> list = IntStream.rangeClosed(1, 10).boxed().collect(Collectors.toList()); List<Integer> subList = list.subList(1, 4); System.out.println(subList); subList.remove(1); System.out.println(subList); System.out.println(list); list.add(0); System.out.println(list); System.out.println(subList); }
Operation results

Result analysis
First point: according to the results, after deleting an element from the subList, the "3" element of the original set list is also missing. After adding an element to the subList, the original set list also has an "14" element. It can be seen that the modification of elements in the set by both parties will affect each other.
Second point: we will find that after adding an element to the list, the output subList is reported with ConcurrentModificationException. According to the error prompt, we find the subList Checkforcomodification(), after entering the method, you will find root modCount != Concurrent modificationexception will be thrown when modcount. debug and see root Modcount = 13 and modCount = 12, so only the modcount value of the list is modified during the add operation on the list, and the modcount value of the subList is not modified.
summary
In order to avoid strong reference to the original set, after obtaining the partitioned list, we do not directly use this set for operation. We can use a new variable to save the partitioned list.
// Method 1 List<Integer> arrayList = new ArrayList<>(rawList.subList(0, 2)); // Method 2 List<Integer> arrayList1 = list.stream().skip(1).limit(3).collect(Collectors.toList());