Java 8 ArrayList hugeCapacity 函数与 MAX_ARRAY_SIZE

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: Java 8 ArrayList hugeCapacity 函数与 MAX_ARRAY_SIZE

1、背景

今天有一个朋友问到一个为什么 ArrayList 源码扩容方法中,数组长度最大值是 MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8 的问题(真的是MAX_ARRAY_SIZE? )。

在这里插入图片描述

并给出下列截图:
在这里插入图片描述

2、别急,让我们捋一捋

我们先搞清楚这里几个关键变量的含义:

  • min capacity 这次扩容最小需要的容量
  • old capacity 扩容前原始数组容量
  • newCapacity = oldCapacity + (oldCapacity >> 1) 是预计要扩容到的容量

在这里插入图片描述

之前讲过,看源码看不太懂时要多看注释。

    /**
     * The maximum size of array to allocate.
     * Some VMs reserve some header words in an array.
     * Attempts to allocate larger arrays may result in
     * OutOfMemoryError: Requested array size exceeds VM limit
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

这里说 Some VMs reserve some header words in an array. 即有些虚拟机会在数组中保存 header words 头部字。

对象头可以看这里:
https://cloud.tencent.com/developer/article/1413543
https://stackoverflow.com/questions/26357186/what-is-in-java-object-header

PS: 数组有点特殊性,数组对象要额外存储数组元素长度在头部,少了这8个长度可能与此有关。

尝试分配大于 MAX_ARRAY_SIZE 长度的数组会导致 OOM (换句话说,超过了该虚拟机的数组长度限制)。

grow 函数的目的是:提高容量以便至少满足最少 minCapacity 容量!!

    /**
     * Increases the capacity to ensure that it can hold at least the
     * number of elements specified by the minimum capacity argument.
     *
     * @param minCapacity the desired minimum capacity
     */
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

有些虚拟机大于 MAX_ARRAY_SIZE (Integer.MAX -8 )就容易OOM (注意只是有些)

注意前提是 new - MAX_ARRAY_SIZE >0 就意味着 正常情况下新的扩容长度大于了 MAX_ARRAY_SIZE。
此时最大可以扩容到 Integer.MAX,因为数组长度是整数。

在这里插入图片描述

因为数组理论上长度就是 Integer.MAX_VALUE 个别JVM 设计上的问题 咱们可以尽量照顾下 但并不是说一定因为个别JVM 就一定不让扩容到 整数最大值长度。

如果再满了 那么对不起 直接到将数组长度设置为整数最大值, 爱咋咋地!

因此,数组最大容量是 Integer.MAX_VALUE (提问的说法有问题) ,在图示情况扩容到 MAX_ARRAY_SIZE 是为了扩容到 MAX_ARRAY_SIZE以上长度就OOM的虚拟机可以尽量不OOM,如果还放不下没办法,对不起了大兄弟!

3 Learn more

你以为这就完了吗?
其实上面的问题并不是重点。
看这这段代码最重点在:
int newCapacity = oldCapacity + (oldCapacity >> 1);

为什么不是扩容到 minCapacity +1?
其实是预先申请这么多的容量,避免频繁扩容,采用了空间换时间思想。
在这里插入图片描述

Redis 的动态字符串和这个有点类似,只不过扩容策略有差异 。

那么为什么Redis 动态字符串扩容会有两个处理方式?为什么大于1M 每次增加1M 而且有最大长度限制呢?
1 首先想下 Redis 和 Java中的ArrayList的使用场景。
Redis 通常用作缓存,而且失效时间相对较长(少则几秒钟,多则几分钟,几个小时等)。而ArrayList 通常在某个函数中用,一般来说生命周期很短,出栈后就可以回收。
2 Redis 为啥要有最大值限制。可能是因为通常和使用者不在同一个服务器上,需要通过网络进行传输,如果很大,传输很容易超时,而且Redis 主任务为单线程,很容易阻塞其他任务的执行。
3 小于1M

4、总结

之前一直提倡看源码一定要重视看注释,当大家 JDK 或者其他开源项目源码有困惑时一定要重视看源码。
同样地,当我们写代码时,有些情况容易让别人困惑时,一定要加上注释。

此外,读源码一定要像这位同学一样,多一些思考。
读源码要读源码的设计理念,体现出来的设计思想。
读源码时建议先猜想后验证,即猜想可能的原因,然后再去分析,这样学到更多。

关于如何高效阅读源码可以看我另外一篇文章。
https://blog.csdn.net/w605283073/article/details/89290798


对了,我最近在 GitChat上写了三篇不错的文章,大家感兴趣可以读读:

《性能优化方法论》
《CodeReview 的正确姿势》
《排错避坑宝典》

相关文章
|
13天前
|
存储 安全 Java
从源码到场景,用 5 分钟讲透 Array 和 ArrayList 的差异
大家好,我是小米,29岁的技术分享者。今天聊聊社招面试中常见的问题——Array和ArrayList的区别。数组是固定大小的容器,长度不可变,性能高;ArrayList是动态数组,可自动扩容,支持更多操作但性能稍逊。在实际开发中,根据需求选择:高性能、固定大小选数组;灵活操作选ArrayList。希望这篇文章能帮你答出漂亮的答案!欢迎关注我的微信公众号“软件求生”,获取更多技术干货。
23 5
|
2月前
|
Java 索引 容器
Java ArrayList扩容的原理
Java 的 `ArrayList` 是基于数组实现的动态集合。初始时,`ArrayList` 底层创建一个空数组 `elementData`,并设置 `size` 为 0。当首次添加元素时,会调用 `grow` 方法将数组扩容至默认容量 10。之后每次添加元素时,如果当前数组已满,则会再次调用 `grow` 方法进行扩容。扩容规则为:首次扩容至 10,后续扩容至原数组长度的 1.5 倍或根据实际需求扩容。例如,当需要一次性添加 100 个元素时,会直接扩容至 110 而不是 15。
Java ArrayList扩容的原理
|
2月前
|
存储 Java 索引
Java中的数据结构:ArrayList和LinkedList的比较
【10月更文挑战第28天】在Java编程世界中,数据结构是构建复杂程序的基石。本文将深入探讨两种常用的数据结构:ArrayList和LinkedList,通过直观的比喻和实例分析,揭示它们各自的优势与局限,帮助你在面对不同的编程挑战时做出明智的选择。
|
3月前
|
安全 Java 程序员
Java集合之战:ArrayList vs LinkedList,谁才是你的最佳选择?
本文介绍了 Java 中常用的两个集合类 ArrayList 和 LinkedList,分析了它们的底层实现、特点及适用场景。ArrayList 基于数组,适合频繁查询;LinkedList 基于链表,适合频繁增删。文章还讨论了如何实现线程安全,推荐使用 CopyOnWriteArrayList 来提升性能。希望帮助读者选择合适的数据结构,写出更高效的代码。
145 3
|
3月前
|
存储 Java
Java“(array) <X> Not Initialized” (数组未初始化)错误解决
在Java中,遇到“(array) &lt;X&gt; Not Initialized”(数组未初始化)错误时,表示数组变量已被声明但尚未初始化。解决方法是在使用数组之前,通过指定数组的大小和类型来初始化数组,例如:`int[] arr = new int[5];` 或 `String[] strArr = new String[10];`。
115 2
|
4月前
|
Java
java基础(12)抽象类以及抽象方法abstract以及ArrayList对象使用
本文介绍了Java中抽象类和抽象方法的使用,以及ArrayList的基本操作,包括添加、获取、删除元素和判断列表是否为空。
45 2
java基础(12)抽象类以及抽象方法abstract以及ArrayList对象使用
|
4月前
|
Java
java基础(11)函数重载以及函数递归求和
Java支持函数重载,即在同一个类中可以声明多个同名方法,只要它们的参数类型和个数不同。函数重载与修饰符、返回值无关,但与参数的类型、个数、顺序有关。此外,文中还展示了如何使用递归方法`sum`来计算两个数之间的和,递归的终止条件是当第一个参数大于第二个参数时。
39 1
java基础(11)函数重载以及函数递归求和
|
3月前
|
Java
让星星⭐月亮告诉你,jdk1.8 Java函数式编程示例:Lambda函数/方法引用/4种内建函数式接口(功能性-/消费型/供给型/断言型)
本示例展示了Java中函数式接口的使用,包括自定义和内置的函数式接口。通过方法引用,实现对字符串操作如转换大写、数值转换等,并演示了Function、Consumer、Supplier及Predicate四种主要内置函数式接口的应用。
35 1
|
3月前
|
Java 编译器 C语言
【一步一步了解Java系列】:Java中的方法对标C语言中的函数
【一步一步了解Java系列】:Java中的方法对标C语言中的函数
37 3
|
3月前
|
算法 Java 测试技术
数据结构 —— Java自定义代码实现顺序表,包含测试用例以及ArrayList的使用以及相关算法题
文章详细介绍了如何用Java自定义实现一个顺序表类,包括插入、删除、获取数据元素、求数据个数等功能,并对顺序表进行了测试,最后还提及了Java中自带的顺序表实现类ArrayList。
53 0

热门文章

最新文章