一、Sublist導致OOM 代碼 @Slf4j public class SubListDemo { public static void subListOOM() { List<List<Integer>> data = new ArrayList<>(); for (int i = 0; i ...
一、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(); }