Why ArrayList.subList Can Leak Memory, Loop Forever, and Crash Your Java App

This article explains how improper use of Java's ArrayList.subList can cause hidden memory leaks, infinite loops, ConcurrentModificationExceptions, and serialization failures in RPC frameworks, and provides concrete best‑practice solutions such as copying to a new list or using stream operations.

Java Backend Technology
Java Backend Technology
Java Backend Technology
Why ArrayList.subList Can Leak Memory, Loop Forever, and Crash Your Java App

1. Memory leak caused by misuse

Running the sample code that repeatedly calls ArrayList.subList creates a SubList object that holds a reference to the original ArrayList. Each SubList keeps the original list in the old generation, so the allFailedList grows until the heap is exhausted even though each individual list only occupies a few megabytes.

public class OrderService {
    public static void main(String[] args) {
        OrderService orderService = new OrderService();
        orderService.process();
    }
    public void process() {
        List<Long> orderIdList = queryOrder();
        List<List<Long>> allFailedList = new ArrayList<>();
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            System.out.println(i);
            List<Long> failedList = doProcess(orderIdList);
            allFailedList.add(failedList);
        }
    }
    private List<Long> doProcess(List<Long> orderIdList) {
        List<Long> failedList = new ArrayList<>();
        for (Long orderId : orderIdList) {
            if (orderId % 2 == 0) {
                failedList.add(orderId);
            }
        }
        return failedList.subList(0, 1);
    }
    private List<Long> queryOrder() {
        List<Long> orderIdList = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            orderIdList.add(RandomUtils.nextLong());
        }
        return orderIdList;
    }
}

The subList implementation creates a SubList that stores a reference to the parent list; therefore every call adds another large object to the old generation.

2. Infinite loop caused by misuse

When a SubList is modified, its add method forwards the operation to the original list, which increases the original list’s size. If the loop iterates over arrayList.size() while the size keeps growing, the loop never terminates.

public class SubListDemo {
    public static void main(String[] args) {
        List<Long> arrayList = init();
        List<Long> subList = arrayList.subList(0, 1);
        for (int i = 0; i < arrayList.size(); i++) {
            if (arrayList.get(i) % 2 == 0) {
                subList.add(arrayList.get(i));
            }
        }
    }
    private static List<Long> init() {
        List<Long> arrayList = new ArrayList<>();
        arrayList.add(RandomUtils.nextLong());
        arrayList.add(RandomUtils.nextLong());
        arrayList.add(RandomUtils.nextLong());
        arrayList.add(RandomUtils.nextLong());
        arrayList.add(RandomUtils.nextLong());
        return arrayList;
    }
}

3. Structural modification restrictions

Modifying a SubList after the parent list has been structurally changed triggers a ConcurrentModificationException. The exception originates from the checkForComodification method, which compares the parent list’s modCount with the sub‑list’s copy.

private void checkForComodification() {
    if (ArrayList.this.modCount != this.modCount)
        throw new ConcurrentModificationException();
}

The JDK documentation defines modCount as the number of structural modifications (operations that change the list size).

4. Serialization failure in RPC

Because ArrayList.SubList does not implement Serializable, attempts to serialize a sub‑list as an RPC argument (e.g., with Dubbo) will fail.

5. Best practices

5.1 Copy to a new list

ArrayList<Object> myArrayList = new ArrayList<>();
ArrayList<Object> part1 = new ArrayList<>(myArrayList.subList(0, 25));
ArrayList<Object> part2 = new ArrayList<>(myArrayList.subList(26, 51));

5.2 Use stream operations

dataList.stream()
        .skip(5).limit(10)
        .collect(Collectors.toList());

dataList.stream()
        .skip(30).limit(10)
        .collect(Collectors.toList());

In summary, ArrayList.subList is intended as a read‑only view. Using it for write‑heavy scenarios leads to memory leaks, endless loops, concurrent‑modification errors, and serialization problems. Prefer copying to a new list or leveraging Java 8 streams for safe, efficient sub‑list handling.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Javaserializationmemory leakArrayListConcurrentModificationExceptionSubList
Java Backend Technology
Written by

Java Backend Technology

Focus on Java-related technologies: SSM, Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading. Occasionally cover DevOps tools like Jenkins, Nexus, Docker, and ELK. Also share technical insights from time to time, committed to Java full-stack development!

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.