JVM运行时数据区(Runtime Data Area)分为JVM栈(Java栈)、堆区(Heap)、方法区、本地方法栈和程序计数器,而Java中的对象和数组(数组可以理解为特殊的对象)是存放在堆区的,这篇文章详细解析对象以及数组在堆区中所占空间大小。
对象在堆区中所占空间大小
结构示意图
先来看一下对象在堆区存储的结构示意图
对象头(Object Head)
标记字和类型指针统称对象头
标记字(Mark Word)
标记字在32位机器占用4字节,在64位机器占用8字节,主要用来表示对象的线程锁状态,还可以用来配合GC、存放该对象的 HashCode。
类型指针(Klass Pointer)
类型指针即当前对象所属类型的指针,封装了语言级别的类信息,例如类名、其修饰符、超类信息等。
加载类的时候类的加载器会将.class字节码文件加载到内存中,即创建一个该类的Class实例,用来表示这个类的元数据,包括常量池、字段、方法等,存放在方法区。
而类型指针(Klass Pointer)就是指向方法区中的Class实例。
类型指针所占的空间大小跟JVM的一个参数“类型指针压缩”(UseCompressedClassPointers)有关,
类型指针压缩参数在开启时类型指针是占4个字节,关闭时类型指针是占8个字节。请注意,该参数是默认开启的,也就是说类型指针默认被压缩为4个字节。(在64位机器上)
在32位机器上,类型指针占用一个字,即4个字节。
实例数据(Instance Data) / 对象体(Object Body)
该部分存储本对象的实例数据(即未被static修饰的变量),实例数据又分为基本数据类型和引用类型。
而静态变量(即被static修饰的变量)不存储在实例对象中,存放在方法区中的Class实例的尾部,被该类的所有对象所共享。
基本数据类型
基本数据类型及所占字节数:
boolean:1字节
char:2字节
byte:1字节
short:2字节
int:4字节
long:8字节
float:4字节
double:8字节
引用数据类型
HotSpot JVM 使用称为普通对象指针 (OOPS, Ordinary Object Pointers) 的数据结构来表示对象指针(即引用数据类型)。它的大小与JVM的另外一个参数“普通对象指针压缩”(UseCompressedOops)有关。
在普通对象指针压缩参数开启时引用占4个字节,关闭时引用占8个字节。
对齐(Padding) / 填充物
由于JVM规定每个对象占用的总空间是8的倍数,故对象占用的总空间(对象头+对象体)不是8的倍数时,会自动补齐字节,使整个对象加上对齐所占用的空间为8的倍数。
例子
public class Test {
int i;
long l;
Long L;
}
在默认开启两个压缩参数时,该类的一个对象在堆占的内存为8(标记字)+4(被压缩的类型指针)+4(int)+8(long)+4(被压缩的包装类引用Long)+4(将前面的28字节对齐,补为8的倍数32字节)
数组在堆区中所占空间大小
结构示意图
先来看一下数组在堆区存储的结构示意图
数组与对象唯一的区别是只比对象多了一个占4字节的数组长度(Length),其他均与对象相同。
例子
默认开启两个压缩参数时,
new Double[10] = 8(Mark Word) + 4(压缩后的Klass Pointer) + 4(Length,4字节长的数组长度记录数组的长度) + 10 * 4(Instance Data,10个被压缩后的包装类引用Double,每个占4字节) + 0(长度为56是8的倍数,不用对齐) = 56 Bytes
new char[7] = 8(Mark Word) + 4(压缩后的Klass Pointer) + 4(Length,4字节长的数组长度记录数组的长度) + 7 * 2(Instance Data,7个基本数据类型char,每个占2字节) + 2(将前面的30字节补位8的倍数32字节) = 32 Bytes