服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Java教程 - 终于明白为啥面试老是有人问 SubList 了,原来这玩意会 OOM!

终于明白为啥面试老是有人问 SubList 了,原来这玩意会 OOM!

2024-03-18 15:49飞天小牛肉 Java教程

在初始化内部类 SubList 的时候传入了 this,这个 SubList 中的 parent 字段就是原始的 List,初始化的时候,并没有把原始 List 中的元素复制到独立的变量中保存,所以双方对元素的修改都会互相影响。而且 SubList 强引用了原始的 List,所

最近刚做到一个内存分页的需求,自测了几次就 OOM 了,找了半天原因,终于把这个坑填上来,下面整理一下发出来,各位小伙伴引以为鉴。

我们经常会使用 List.subList 方法对 List 进行切片,比如取前十个元素出来用,但是和 Arrays.asList 的问题类似(具体文章可以看 慎用 ArrayList,全是坑!),List.subList 返回的子 List 不是一个全新地址的 ArrayList,这个子 List 会和原始 List 相互影响。

如果不注意,很可能会因此产生 OOM 问题。

话不多说,先复现问题。如下代码所示,定义一个名为 data 的静态 List 用来存放 List<Integer> 类型,循环 1000 次,每次都从一个具有 100 万个 Integer 的 List 中(即代码中的 rawList),使用 subList 方法获得一个只包含一个数字的子 List,并把这个子 List 加入 data 变量:

终于明白为啥面试老是有人问 SubList 了,原来这玩意会 OOM!图片

看起来,这个 data 变量里面最终保存的只是 1000 个具有 1 个元素的 List 而已,并不会出现什么问题啊。

但是,代码在运行到一段时间后,可以看到在我的机器上是第 159 次循环后发生了 OOM:

终于明白为啥面试老是有人问 SubList 了,原来这玩意会 OOM!图片

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

subList 返回的子 List 为啥会强引用原始的 List?再来做个实验看下:

首先初始化一个包含数字 1 到 10 的 ArrayList,然后通过调用 subList 方法取出 2、3、4,随后删除这个 SubList 中的元素数字 3。可以看到原始 List 中数字 3 被删除了,说明删除子 List 中的元素影响到了原始 List:

终于明白为啥面试老是有人问 SubList 了,原来这玩意会 OOM!图片

终于明白为啥面试老是有人问 SubList 了,原来这玩意会 OOM!图片

继续看,我们为原始的 ArrayList 增加一个元素数字 0,然后遍历 SubList 输出所有元素。代码运行后报错 java.util.ConcurrentModificationException:

终于明白为啥面试老是有人问 SubList 了,原来这玩意会 OOM!图片

终于明白为啥面试老是有人问 SubList 了,原来这玩意会 OOM!图片

分析下 ArrayList 的源码,看看为什么会是这样:

终于明白为啥面试老是有人问 SubList 了,原来这玩意会 OOM!图片

  1. ArrayList 维护了一个 int 类型的 modCount 的字段,表示 List 结构性修改的次数。所谓结构性修改,指的是影响 List 大小的修改,所以 add 操作必然会改变 modCount 的值。
  2. 分析 subList 方法可以看到,获得的 List 其实是创建了一个内部类 SubList,并不是普通的 ArrayList。
  3. 在初始化内部类 SubList 的时候传入了 this,这个 SubList 中的 parent 字段就是原始的 List,初始化的时候,并没有把原始 List 中的元素复制到独立的变量中保存,所以双方对元素的修改都会互相影响。而且 SubList 强引用了原始的 List,所以大量保存这样的 SubList 其实也保存了大量原始的 List,从而导致 OOM。
  4. 分析 listIterator 方法可知,遍历 SubList 的时候会先获得迭代器,比较原始 ArrayList modCount 的值和 SubList 当前 modCount 的值,如果不想等,就会抛出 ConcurrentModificationException 异常。所以上述实验代码,我们在获得了 SubList 为原始 List 新增了一个元素,修改了原始 List 的 modCount,所以判等失败抛出异常。

综上,既然 SubList 和原始 List 会相互影响,那么避免相互影响的修复方式有两种:

  1. 不直接使用 subList 方法返回的 SubList,而是重新使用 new ArrayList,在构造方法传入 SubList,来构建一个独立的 ArrayList:
List<Integer> subList = new ArrayList<>(list.subList(1, 4));
  1. 对于 Java 8 使用 Stream 的 skip 和 limit API 来跳过流中的元素,以及限制流中元素的个数,同样可以达到 SubList 切片的目的:
List<Integer> subList = list.stream().skip(1).limit(3).collect(Collectors.toList());

原文地址:https://mp.weixin.qq.com/s/KrTMt1bcFYgUE_RvYfzY1Q

延伸 · 阅读

精彩推荐
  • Java教程解决阿里云OSS使用URL无法访问图片的两种方法

    解决阿里云OSS使用URL无法访问图片的两种方法

    这篇文章主要介绍了解决阿里云OSS使用URL无法访问图片的两种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,...

    北海道恋人6632020-08-11
  • Java教程SpringMVC 拦截器不拦截静态资源的三种处理方式方法

    SpringMVC 拦截器不拦截静态资源的三种处理方式方法

    本篇文章主要介绍了SpringMVC 拦截器不拦截静态资源的三种处理方式方法,详细的介绍了三种方法,有兴趣的可以了解一下。 ...

    路伟8962020-07-29
  • Java教程关于spring中定时器的使用教程

    关于spring中定时器的使用教程

    大家应该都有所体会,在很多实际的web应用中,都有需要定时实现的服务,下面这篇文章主要给大家介绍了关于spring中定时器的使用教程,对大家具有一定...

    阿木侠2752020-11-23
  • Java教程Java超详细讲解如何生成随机整数

    Java超详细讲解如何生成随机整数

    在 Java 中,生成随机数的场景有很多,所以本文我们就来盘点一下 几种生成随机数的方式,以及它们之间的区别和每种生成方式所对应的场景...

    程序媛_小白6472022-12-14
  • Java教程springboot集成redis lettuce

    springboot集成redis lettuce

    目前java操作redis的客户端有jedis跟Lettuce。本文主要介绍了springboot集成redis lettuce,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙...

    liuhmmjj10152022-01-20
  • Java教程Java ArrayList 数组之间相互转换

    Java ArrayList 数组之间相互转换

    本文通过代码示例给大家讲解arraylist转化为数组,然后数组转化为arraylist的相关资料,感兴趣的朋友一起看看吧 ...

    mrr4812020-01-20
  • Java教程Java注解方式之防止重复请求

    Java注解方式之防止重复请求

    这篇文章主要介绍了关于Java注解方式防止重复请求,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...

    grace.free6692021-12-31
  • Java教程Java关于JDK1.8中的Optional类

    Java关于JDK1.8中的Optional类

    本文主要介绍了Optional类的一些常用方法,以及其应用场景,其主要是为了规避空指针异常(NPE)。熟练的运用Optional类可以很大的简化我们的代码,使代码...

    码农飞哥6062021-12-18