集合类对象的最大大小?
#jdk源码解读 #jvm 要探讨这个问题,首先要知道对象在内存中的模样
对象的组成
对象在堆中由对象头,实例字段,对齐和填充组成。
对象头
对象头通常根据JVM不同而不同:
| 32位JVM | 64位JVM(无指针压缩) | |
|---|---|---|
| 对象头占用 | 8字节=4字节MarkWord+4字节类指针 | 12字节=8字节MarkWord+4字节类指针 |
实例字段
实例字段最好理解,就是对象内部持有的字段,分为基础类型和引用类型。
| 大小(对齐后) | |
|---|---|
| int | 4字节 |
| long | 8字节 |
| double | 8字节 |
| float | 4字节 |
| char | 2字节 |
| bool | 1字节 |
| byte | 1字节 |
| short | 2字节 |
| 引用 | 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内存分配策略]]
所以知道容量限制,又知道了内存占用计算方法,很容易就能算出来啦