• Java List的SubList使用问题


    一、Sublist导致OOM

    代码

    @Slf4j
    public class SubListDemo {
    
        public static void subListOOM() {
            List<List<Integer>> data = new ArrayList<>();
            for (int i = 0; i < 1000; i++) {
                List<Integer> rawList = IntStream.rangeClosed(1, 100000).boxed().collect(Collectors.toList());//构建一个100000个元素的list
                data.add(rawList.subList(0, 1));
            }
            log.info("data.size(): " +data.size());
        }
    }

    OOM

    Exception in thread "File Watcher" java.lang.OutOfMemoryError: GC overhead limit exceeded
        at java.io.File.listFiles(File.java:1212)
        at org.springframework.boot.devtools.filewatch.FolderSnapshot.collectFiles(FolderSnapshot.java:63)
        at org.springframework.boot.devtools.filewatch.FolderSnapshot.collectFiles(FolderSnapshot.java:67)
        at org.springframework.boot.devtools.filewatch.FolderSnapshot.collectFiles(FolderSnapshot.java:67)
        at org.springframework.boot.devtools.filewatch.FolderSnapshot.collectFiles(FolderSnapshot.java:67)
        at org.springframework.boot.devtools.filewatch.FolderSnapshot.<init>(FolderSnapshot.java:58)
        at org.springframework.boot.devtools.filewatch.FileSystemWatcher$Watcher.getCurrentSnapshots(FileSystemWatcher.java:277)
        at org.springframework.boot.devtools.filewatch.FileSystemWatcher$Watcher.scan(FileSystemWatcher.java:251)
        at org.springframework.boot.devtools.filewatch.FileSystemWatcher$Watcher.run(FileSystemWatcher.java:236)
        at java.lang.Thread.run(Thread.java:748)
    Exception in thread "restartedMain" java.lang.reflect.InvocationTargetException
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49)
    Caused by: java.lang.OutOfMemoryError: GC overhead limit exceeded
        at java.lang.Integer.valueOf(Integer.java:832)
        at java.util.stream.IntPipeline$$Lambda$492/1604894031.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:481)
        at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
        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.example.newdemo.SubListDemo.subListOOM(SubListDemo.java:17)
        at com.example.newdemo.NewdemoApplication.main(NewdemoApplication.java:13)

    分析

    出现 OOM 的原因是,循环中的 1000 个具有 10 万个元素的 List 始终得不到回收,因为它始终被 subList 方法返回的 List 强引用。

        public List<E> subList(int fromIndex, int toIndex) {
            subListRangeCheck(fromIndex, toIndex, size);
            return new SubList(this, 0, fromIndex, toIndex);
        }

    parent 字段就是原始的 List。SubList没有copy一份自己的数据,而是完整的保留了原始的list。 SubList 是原始 List 的视图,并不是独立的 List, SubList 强引用了原始的 List,所以大量保存这样的 SubList 会导致 OOM。

    解决

    不直接使用 subList 方法返回的 SubList,而是重新使用 new ArrayList,在构造方法传入 SubList,来构建一个独立的 ArrayList。sublist直接释放-》原始的list也被释放。

       public static void subListWithoutOOM() {
            List<List<Integer>> data = new ArrayList<>();
            for (int i = 0; i < 1000; i++) {
                List<Integer> rawList = IntStream.rangeClosed(1, 100000).boxed().collect(Collectors.toList());//构建一个100000个元素的list
                data.add(new ArrayList<>(rawList.subList(0, 1)));
            }
            log.info("data.size(): " +data.size());
        }

    另外一个例子

       public static void removeSubList() {
            List<Integer> rawList = IntStream.rangeClosed(1, 10).boxed().collect(Collectors.toList());
            List<Integer> subList = rawList.subList(0, 3);
            subList.remove(0);
            rawList.forEach(System.out::print);
        }
    2345678910

    可以看到,移除sublist的元素后,直接影响到了原始list。

    二、修改原始列表后SubList循环报错

    测试代码

     public static void addItemToOriginalList() {
            List<Integer> rawList = IntStream.rangeClosed(1, 10).boxed().collect(Collectors.toList());
            List<Integer> subList = rawList.subList(0, 3);
    
            rawList.add(11);
            try {
                subList.forEach(System.out::print);
            }
            catch (Exception ex)
            {
                ex.printStackTrace();
            }
        }
    java.util.ConcurrentModificationException
        at java.util.ArrayList$SubList.checkForComodification(ArrayList.java:1239)
        at java.util.ArrayList$SubList.listIterator(ArrayList.java:1099)
        at java.util.AbstractList.listIterator(AbstractList.java:299)
        at java.util.ArrayList$SubList.iterator(ArrayList.java:1095)
        at java.lang.Iterable.forEach(Iterable.java:74)
        at com.example.newdemo.SubListDemo.addItemToOriginalList(SubListDemo.java:46)
        at com.example.newdemo.NewdemoApplication.main(NewdemoApplication.java:13)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49)

    分析

    modCount 的字段,表示结构性修改的次数--影响list size修改测次数,add肯定影响size,导致modCount加1.但是sublist的modCount没有变,所有抛出了异常。

    代码

     private void checkForComodification() {
                if (ArrayList.this.modCount != this.modCount)
                    throw new ConcurrentModificationException();
            }
  • 相关阅读:
    P3619 魔法
    【HAOI2014】遥感监测
    cdcq的独立博客上线辣!-> http://cdcq.coding.me/blog/
    重复型图床
    【BZOJ1213】高精度开根
    前后端技术
    【HAOI2011】problem b
    【HAOI2011】problem a
    【BZOJ4553】【TJOI2016】【HEOI2016】序列
    【HAOI2015】 T1
  • 原文地址:https://www.cnblogs.com/Brake/p/12732929.html
Copyright © 2020-2023  润新知