第十章 Java 对象内存布局和对象头
10.1 面试题
-
说一下 JUC,AQS 的大致流程
-
CAS 自旋锁,是获取不到就一直自旋吗?CAS 和 synchronized 区别在哪里,为什么 CAS 好,具体优势在哪里?
-
synchronized 底层是如何实现的,实现同步的时候用到 CAS 了吗?具体哪里用到了?
-
对象头存储哪些信息?长度是多少位存储?
10.2 Object object = new Object() 谈谈你对这句话的理解
位置所在:
-
object 引用变量:存储在 Java 栈的本地变量表中(如果该语句位于方法体内),它是一个指向堆中对象的引用
-
new Object()创建对象实例:分配在堆内存的新生代伊甸园区(Eden Space)中 -
对象类型信息:存储在方法区,包括类元数据(如类名、父类、接口、方法等)
构成布局:
对象头、实例数据、对齐填充
10.3 对象在堆内存中布局
10.3.1 权威定义----周志明老师 JVM
在 HotSpot 虚拟机里,对象在堆内存和存储布局可以划分为三个部分:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)

对象内部结构分为:对象头、实例数据、对齐填充(保证 8 个字节的倍数)
对象头分为对象标记(markOop)和类元信息(klassOop),类元信息存储的是指向该对象类元数据(klass)的首地址
10.3.2 对象在堆内存中的存储布局
对象头(Header)
对象标记(Mark Word)


对象头(在 64 位系统中,Mark Word 占了 8 个字节,类型指针占了 8 个字节,一共是 16 个字节)
在 54 位虚拟机下,Mark Word 是 64 bit 大小的,其存储结构如表 2-5 所示:

-
默认存储对象的 HashCode、分代年龄和锁标志等信息
-
这些信息都是与对象自定义无关的数据,所以 Mark Word 被设计成一个非固定的数据结构以便在极小的空间内存存储尽量多的数据
-
它会根据对象的状态复用字节的存储空间,也就是说在运行期间 Mark Word 里存储的数据会随着锁标志的变化而变化
类元信息(类型指针)(Class Pointer)

- 对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象哪个类的实例
对象头多大
在 64 位系统中,Mark Word 占了 8 个字节,类型指针占了 8 个字节,一共是 16 个字节
实例数据(Instance Data)
- 存放类的属性(Field)数据信息,包括父类的属性信息
对齐填充(Padding)(保证 8 个字节的倍数)
- 虚拟机要求对象起始地址必须是 8 字节的整数倍,填充数据不是必须存在的,仅仅是为了字节对齐,这部分内存按 8 字节补充对齐
public class ObjectHeadDemo {
public static void main(String[] args) {
Object o = new Object();//new 一个对象,占内存多少?8 + 8 = 16 或 8 + 4 + 4(两种情况,但结果相同,取决于是否开启压缩指针,第一种情况:对象标记 8 + 类型指针 8 = 16;第二种情况:对象标记 8 + 类型指针 4 + 对齐补充 4 = 16 )
System.out.println(o.hashCode());//这个 hashCode 记录在对象的什么地方?对象头里的对象标记中
synchronized (o) {
}
System.gc();//手动收集垃圾..... 15 次可以从新生代----老年代
Customer c1 = new Customer();
}
}
/**
* 只有一个实例的对象(暂时忽略压缩指针的影响)
* 对象头:
* 对象标记:8 字节
* 类型指针:8 字节
* 对象标记 + 类型指针 = 16 字节
* 实例数据:
* int id :4 字节
* boolean flag :1 字节
* 对象头 + 实例数据 = 21 字节
* 对齐填充(8 字节的整数倍):3 字节
* 总共:21 + 3 = 24 字节
*/
class Customer {
int id;
boolean flag;
}
10.4 再说对象头的 Mark Word

对象布局、GC 回收和后面的锁升级就是对象标记 Mark Word 里面标志位的变化
10.5 聊聊 Object obj = new Object()
10.5.1 运行结果展示
JOL 证明
分析对象在 JVM 中的大小和分布
POM
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.9</version>
</dependency
小试一下
public class JOLDemo {
/**
* 运行结果:
* # Running 64-bit HotSpot VM.
* # Using compressed oop with 3-bit shift.
* # Using compressed klass with 3-bit shift.
* # Objects are 8 bytes aligned.
* # Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
* # Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
*
* 8
*/
public static void main(String[] args) {
//VM 的细节详细情况
System.out.println(VM.current().details());
//所有的对象分配的字节都是 8 的整数倍
System.out.println(VM.current().objectAlignment());
}
}
证明:
Object 的大小
public class JOLDemo {
/**
* 运行结果:
* java.lang.Object 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) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
* 12 4 (loss due to the next object alignment)
* Instance size: 16 bytes
* Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
*/
public static void main(String[] args) {
Object o = new Object();
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
运行结果:

类型说明:
| 变量 | 说明 |
|---|---|
| OFFSET | 偏移量,也就是这个字段位置所占用的 byte 数 |
| SIZE | 后面类型的字节大小 |
| TYPE | 是 Class 中定义的类型 |
| DESCRIPTION | DESCRIPTION 是类型的描述 |
| VALUE | VALUE 是 TYPE 在内存中的值 |
对象的大小
没有其他任何实例数据
public class JOLDemo {
/**
* 运行结果:
* objecthead.Customer 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) 43 c0 00 f8 (01000011 11000000 00000000 11111000) (-134168509)
* 12 4 (loss due to the next object alignment)
* Instance size: 16 bytes
* Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
*/
public static void main(String[] args) {
Object o = new Object();
// System.out.println(ClassLayout.parseInstance(o).toPrintable());
Customer c1 = new Customer();
System.out.println(ClassLayout.parseInstance(c1).toPrintable());
}
}
/**
* 只有一个实例的对象
* 对象头:
* 对象标记:8 字节
* 类型指针:4 字节
* 对象标记 + 类型指针 = 12 字节
* 实例数据:
* 无
* 对象头 + 实例数据 = 12 字节
* 对齐填充(8 字节的整数倍):4 字节
* 总共:12 + 4 = 16 字节
*/
class Customer {
//第一种情况:只有对象头,没有其他任何实例数据
}
运行结果:

存在实例数据
public class JOLDemo {
/**
* 运行结果:
* objecthead.Customer 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) 43 c0 00 f8 (01000011 11000000 00000000 11111000) (-134168509)
* 12 4 int Customer.id 0
* 16 1 boolean Customer.flag false
* 17 7 (loss due to the next object alignment)
* Instance size: 24 bytes
* Space losses: 0 bytes internal + 7 bytes external = 7 bytes total
*/
public static void main(String[] args) {
Object o = new Object();
// System.out.println(ClassLayout.parseInstance(o).toPrintable());
Customer c1 = new Customer();
System.out.println(ClassLayout.parseInstance(c1).toPrintable());
}
}
/**
* 只有一个实例的对象
* 对象头:
* 对象标记:8 字节
* 类型指针:4 字节
* 对象标记 + 类型指针 = 12 字节
* 实例数据:
* int id :4 字节
* boolean flag :1 字节
* 对象头 + 实例数据 = 17 字节
* 对齐填充(8 字节的整数倍):7 字节
* 总共:17 + 7 = 24 字节
*/
class Customer {
//第一种情况:只有对象头,没有其他任何实例数据
//第二种情况:int + boolean,满足对齐填充 24 bytes
int id;
boolean flag;
}
运行结果:

GC 年龄采用 4 位 bit 存储,最大为 15
例如 MaxTenuringThreshold 参数默认值就是 15
将值设置为 16
-XX:MaxTenuringThreshold= 16
出现此错误:
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.
Improperly specified VM option 'MaxTenuringThreshold='
10.5.2 压缩指针
压缩指针相关说明命令
查看当前虚拟机信息:
java -XX:+PrintCommandLineFlags -version

-XX:+UserCompressedClassPointers
默认开启压缩说明,开启后将类型指针压缩为 4 字节,以节约空间
手动关闭
-XX:-UserCompressedClassPointers
总结
Object 对象多大?分两种情况:
- 默认配置,启动了压缩指针:8(对象标记) + 4(类型指针) + 4(对齐填充) = 一个对象 16 字节
- 手动配置,关闭了压缩指针:8(对象标记) + 8(类型指针) = 一个对象 16 字节

浙公网安备 33010602011771号