环境: 本机为64位操作系统。jdk 1.8

一、对象在内存中存储的布局可以分为3块区域:对象头(Header),实例数据(Instance Data),对齐填充(Padding)。

二、我们可以通过maven工程引用对应的 jol-core jar包。将对应的内存分布打印出来做相应的研究。

我这里写勒一个类:User,4个字段 占用 4*4 byte =16 byte 共16字节 (实例数据占用16字节)

java 内存对齐 向量指令 java 对象内存布局_java 内存对齐 向量指令

ObjectHeader类的打印对象的整体分布:主要代码如下:

System.out.println(ClassLayout.parseInstance(user).instanceSize());

System.out.println(ClassLayout.parseInstance(user).toPrintable());

java 内存对齐 向量指令 java 对象内存布局_JVM_02

产生结果:

java 内存对齐 向量指令 java 对象内存布局_sed_03

上图中:可以看到,对象在内存中分为3块区域。 对齐填充 在JVM中定义为 8byte 的 整数倍。 所以 28 % 8 =4 所以需要padding 4 byte 。

Header包含如下内容:主要由 Mark World (8byte)+ Class Pointer(4byte) (类型指针)

1. Mark World  根据  对象 lock 标记的状态不同 , 所存储的内容也会发生改变. 具体如下图.

|------------------------------------------------------------------------------|--------------------|
| Mark Word (64 bits) | State |
|------------------------------------------------------------------------------|--------------------|
| unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:1 | lock:2 | Normal |
|------------------------------------------------------------------------------|--------------------|
| thread:54 | epoch:2 | unused:1 | age:4 | biased_lock:1 | lock:2 | Biased |
|------------------------------------------------------------------------------|--------------------|
| ptr_to_lock_record:62 | lock:2 | Lightweight Locked |
|------------------------------------------------------------------------------|--------------------|
| ptr_to_heavyweight_monitor:62 | lock:2 | Heavyweight Locked |
|------------------------------------------------------------------------------|--------------------|
| | lock:2 | Marked for GC |
|------------------------------------------------------------------------------|--------------------|
biased_lock
lock
状态
0
01
无锁
1
01
偏向锁
0
00
轻量级锁
0
10
重量级锁
0
11

GC标记

lock:2位的锁状态标记位,由于希望用尽可能少的二进制位表示尽可能多的信息,所以设置了lock标记。该标记的值不同,整个mark word表示的含义不同。 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的指针。

2. Class Pointer (类型指针) 这一部分用于存储对象的类型指针,该指针指向它的类元数据,JVM通过这个指针确定对象是哪个类的实例。该指针的位长度为JVM的一个字大小,即32位的JVM为32位,64位的JVM为64位。

如果应用的对象过多,使用64位的指针将浪费大量内存,统计而言,64位的JVM将会比32位的JVM多耗费50%的内存。为了节约内存可以使用选项+UseCompressedOops开启指针压缩,其中,oop即ordinary object pointer普通对象指针。开启该选项后,下列指针将压缩至32位:

每个Class的属性指针(即静态变量)

每个对象的属性指针(即对象变量)

普通对象数组的每个元素指针

上例证:

禁用指针压缩: -XX:-UseCompressedOops

java 内存对齐 向量指令 java 对象内存布局_java分块布局_04

开启指针压缩,jvm默认开启

java 内存对齐 向量指令 java 对象内存布局_JVM_05

当然,也不是所有的指针都会压缩,一些特殊类型的指针JVM不会优化,比如指向PermGen的Class对象指针(JDK8中指向元空间的Class对象指针)、本地变量、堆栈元素、入参、返回值和NULL指针等。

3. 如果对象是一个数组,那么对象头还需要有额外的空间用于存储数组的长度,这部分数据的长度也随着JVM架构的不同而不同:32位的JVM上,长度为32位;64位JVM则为64位。64位JVM如果开启+UseCompressedOops选项,该区域长度也将由64位压缩至32位。