对象的内存布局
HotSpot虚拟机中,对象在内存中存储的布局可以分为三块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。
在java中对象的内存布局分为两种情况,非数组对象和数组对象,数组对象和非数组对象的区别就是需要额外的空间存储数组的长度length。
对象头
对象头又分为MarkWord和Class Pointer两部分。
- MarkWord:包含一系列的标记位,比如轻量级锁的标记位,偏向锁标记位,gc记录信息等等,在32位系统占4字节,在64位系统中占8字节。
- ClassPointer:用来指向对象对应的Class对象(其对应的元数据对象)的内存地址。在32位系统占4字节,在64位系统中占8字节。
- Length:只在数组对象中存在,用来记录数组的长度,占用4字节
Instance data
Instance data:对象实际数据,对象实际数据包括了对象的所有成员变量,其大小由各个成员变量的大小决定。(这里不包括静态成员变量,因为其是在方法区维护的)
Padding
Padding:Java对象占用空间是8字节对齐的,即所有Java对象占用bytes数必须是8的倍数,是因为当我们从磁盘中取一个数据时,不会说我想取一个字节就是一个字节,都是按照一块儿一块儿来取的,这一块大小是8个字节,所以为了完整,padding的作用就是补充字节,保证对象是8字节的整数倍。
指针压缩
-XX:+UseCompressedOops
可以压缩指针,将占用的空间压缩为原来的一半,起到节约空间的作用,classpointer参数大小就受到其影响。
Object o = new Object()到底占用多少个字节?
- 在开启指针压缩的情况下,markword占用8字节,classpoint占用4字节,Instance data无数据,总共是12字节,由于对象需要为8的整数倍,Padding会补充4个字节,总共占用16字节的存储空间。
- 在没有指针的情况下,markword占用8字节,classpoint占用8字节,Instance data无数据,总共是16字节。
OpenJDK,提供了JOL包,可以帮我们在运行时计算某个对象的大小。
添加依赖:
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.14</version>
</dependency>
查看对象的内部信息:
public static void main(String[] args) {
System.out.println(ClassLayout.parseInstance(new App()).toPrintable());
}
输出:
com.fang.App object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)
12 4 int App.a 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
分析:
JVM中,每个对象都有一个对象头,由标记字段和类型指针构成,标记字段存储hashCode, GC信息,锁信息,类型指针指向该对象的类。
- Mark Word(标记字段):对象的Mark Word部分占8个字节(64位虚拟机),其内容是一系列的标记位,比如轻量级锁的标记位,偏向锁标记位等等。
- Class Pointer(Class对象指针):Class对象指针的大小也是4个字节,其指向的位置是对象对应的Class对象(其对应的元数据对象)的内存地址
- 对象实际数据:这里面包括了对象的所有成员变量,其大小由各个成员变量的大小决定,比如:byte和boolean是1个字节,short和char是2个字节,int和float是4个字节,long和double是8个字节,reference是4个字节
- 对齐:最后一部分是对齐填充的字节,按8个字节填充。