在 Hotspot
虚拟机中,对象在内存中的布局分为三块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding);
从上面的这张图里面可以看出,对象在内存中的结构主要包含以下几个部分:
- Mark Word(标记字段):对象的Mark Word部分占4个字节,其内容是一系列的标记位,比如轻量级锁的标记位,偏向锁标记位等等。
- Klass Pointer(Class对象指针):Class对象指针的大小也是4个字节,其指向的位置是对象对应的Class对象(其对应的元数据对象)的内存地址
- 对象实际数据:这里面包括了对象的所有成员变量,其大小由各个成员变量的大小决定,比如:byte和boolean是1个字节,short和char是2个字节,int和float是4个字节,long和double是8个字节,reference是4个字节
- 对齐:最后一部分是对齐填充的字节,按8个字节填充。
Java 对象头是实现 synchronized
的锁对象的基础,一般而言,synchronized
使用的锁对象是存储在 Java 对象头里。它是轻量级锁和偏向锁的关键
JVM 中每个对象都有一个对象头(Objectheader),普通对象头的长度为两个机器码,数组对象头的长度为三个机器码
在32位虚拟机中,1个机器码等于4字节,也就是32 bit
对象头的形式
普通对象头:
1 | |--------------------------------------------------------------| |
数组对象头:
1 | |---------------------------------------------------------------------------------| |
对象头的组成
Mark Word
Mark Word 用于存储对象自身的运行时数据,如哈希码(HashCode)、GC 分代年龄、锁状态标志、线程持有的
锁、偏向线程 ID、偏向时间戳等等;Mark Word 的位长度为 JVM 的一个 Word 大小,也就是说32位 JVM 的 Mark Word 为32位,64位 JVM 为64位。
对象需要存储的运行时数据很多,考虑到虚拟机的空间效率,Mark Word 被设计成一个非固定的数据结构以便在极小的空间内存储尽量多的信息,它会根据对象的状态复用自己的存储空间;JVM 将字的最低两个位设置为标记位,不同标记位下的 Mark Word 示意如下:
1 | |-------------------------------------------------------|--------------------| |
其中各部分的含义如下:
lock:2位的锁状态标记位,由于希望用尽可能少的二进制位表示尽可能多的信息,所以设置了 lock 标记。该标记的值不同,整个 Mark Word 表示的含义不同。
biased_lock | lock | 状态 |
---|---|---|
0 | 01 | 无锁 |
1 | 01 | 偏向锁 |
0 | 00 | 轻量级锁 |
0 | 10 | 重量级锁 |
0 | 11 | GC标记 |
biased_lock:对象是否启用偏向锁标记,只占1个二进制位。为1时表示对象启用偏向锁,为0时表示对象没有偏向锁。
age:4位的Java对象年龄。在GC中,如果对象在Survivor区复制一次,年龄增加1。当对象达到设定的阈值时,将会晋升到老年代。默认情况下,并行GC的年龄阈值为15,并发GC的年龄阈值为6。由于age只有4位,所以最大值为15,这就是-XX:MaxTenuringThreshold
选项最大值为15的原因。
identity_hashcode:25位的对象标识Hash码,采用延迟加载技术。调用方法System.identityHashCode()
计算,并会将结果写到该对象头中。当对象被锁定时,该值会移动到管程Monitor中。
thread:持有偏向锁的线程ID。
epoch:偏向时间戳。
ptr_to_lock_record:指向栈中锁记录的指针。
ptr_to_heavyweight_monitor:指向管程Monitor的指针。
64位下的标记字与32位的相似,不再赘述:
1 | |------------------------------------------------------------------------------|--------------------| |
Klass Pointer(Class对象指针)
这一部分用于存储对象的类型指针,该指针指向它的类元数据,JVM通过这个指针确定对象是哪个类的实例。该指针的位长度为JVM的一个字大小,即32位的JVM为32位,64位的JVM为64位。
如果应用的对象过多,使用64位的指针将浪费大量内存,统计而言,64位的JVM将会比32位的JVM多耗费50%的内存。为了节约内存可以使用选项+UseCompressedOops
开启指针压缩,其中,oop即ordinary object pointer普通对象指针。开启该选项后,下列指针将压缩至32位:
- 每个Class的属性指针(即静态变量)
- 每个对象的属性指针(即对象变量)
- 普通对象数组的每个元素指针
当然,也不是所有的指针都会压缩,一些特殊类型的指针JVM不会优化,比如指向PermGen的Class对象指针(JDK8中指向元空间的Class对象指针)、本地变量、堆栈元素、入参、返回值和NULL指针等。
array length
如果对象是一个数组,那么对象头还需要有额外的空间用于存储数组的长度,这部分数据的长度也随着JVM架构的不同而不同:32位的JVM上,长度为32位;64位JVM则为64位。64位JVM如果开启+UseCompressedOops
选项,该区域长度也将由64位压缩至32位。
参考文章: https://blog.csdn.net/zhoufanyang_china/article/details/54601311
v1.5.2