集合类对象的最大大小?

120 阅读4分钟

集合类对象的最大大小?

#jdk源码解读 #jvm 要探讨这个问题,首先要知道对象在内存中的模样

对象的组成

对象在堆中由对象头,实例字段,对齐和填充组成。

对象头

对象头通常根据JVM不同而不同:

32位JVM64位JVM(无指针压缩)
对象头占用8字节=4字节MarkWord+4字节类指针12字节=8字节MarkWord+4字节类指针

实例字段

实例字段最好理解,就是对象内部持有的字段,分为基础类型和引用类型。

大小(对齐后)
int4字节
long8字节
double8字节
float4字节
char2字节
bool1字节
byte1字节
short2字节
引用4字节(32位或压缩指针),8字节(64位)

对齐与填充

什么是对齐?

字节对齐是指在内存中存储数据时,将数据放置在满足其大小的倍数地址上。例如,一个4字节的int通常需要放在4字节对齐的地址(如0x00、0x04、0x08等)上。这是因为计算机内存是按照特定字节数访问的,对齐可以提高CPU访问内存的速度。对齐规则通常为:

  • 数据类型的对齐要求是其自身大小的倍数(例如int类型通常4字节,需要4字节对齐)。
  • 结构体的总对齐是其中最大成员的对齐大小。
什么是填充?

为了实现对齐,编译器在结构体或类成员之间添加一些空闲字节(填充字节),确保每个成员都位于合适的地址上。填充会影响结构体的大小,使其可能比成员的总大小还大。

public class Example {
    byte a;//起始位置0x00
    int b;//起始位置0x04
    short c;//起始位置0x08
}
  • byte a之后,插入了3个填充字节,使int b在4字节对齐的地址上。
  • short c之后,可能插入额外的填充字节,使对象的总大小为4的倍数,符合最大成员int b的对齐要求。 所以整个对象的大小=12字节(对象头)+ 12字节(对齐后的字段)=24字节。

来点更复杂的案例:

public class Person{
    char[] a;
    
    Person(char x,char y,char z){
        a = new char[]{x,y,z};
    }

    public static void main(String[] args) {
        Person person = new Person('x', 'y', 'z');
    }
}

person对象的大小是多少呢?我们知道a实际上是一个引用,指向实际的数组。引用为8字节,实际数组数据的大小为3×2字节=6字节,数组也是对象,有自己的对象头12字节,person对象头为12字节,故总共38字节。

集合类对象的大小

终于可以进入正题了,集合类中主要包含的信息就是集合元素本身。无论是List还是Map等,集合类底层通常都通过数组来储存元素。集合类通常都有泛型,底层数组的类型通常都是这个泛型,所以单个元素的大小是不确定的。 集合类中元素最多有多少个?这个问题需要先从Java数组限制开始探讨。Java数组索引采用int,所以Java数组大小最大为2^31 - 1。实际上各个实现类中数组的最大大小都小于这个值。如HashTable为2^31 - 8,预留了一丢丢位置。ArrayList中书面记录了最大值为2^31 - 8,实际上可以到达2^31 - 1。HashMap由于设计原因,桶数组的大小只能是二次幂,容量最大只能到2^30。

JVM限制

HotSpot JVM 中,堆大小的默认限制取决于多个因素,包括操作系统的架构(32位 vs 64位)、JVM的版本、物理内存的大小以及JVM的默认配置。堆大小不仅受到JVM启动时的参数设置的影响(如 -Xms-Xmx)。 HotSpot JVM默认将堆大小设置为系统总内存的一部分,通常为 1/4到1/2的物理内存。但是,JVM的默认堆大小是有上限的,并且受操作系统内存分配的限制。对于大多数64位系统,可以使用 -Xmx 参数将堆最大大小设置为几十GB甚至上百GB,前提是物理内存和操作系统支持。 具体可以参阅[[JVM内存分配策略]]

所以知道容量限制,又知道了内存占用计算方法,很容易就能算出来啦