对象的内存布局分为两种,普通对象和数组对象
用于存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持久的锁、偏向线程的ID等,通过存储的内容得知对象头是锁机制和GC的重要基础。
以 32位虚拟机为例,来看一下其 Mark Word 的字节具体是如何分配的?
Class Pointer是对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
64 位的JVM默认开启-XX:+UseCompressedClassPointers压缩类型指针开关,由原8字节压缩到4字节大小
疑惑点:很多文章都写数组长度是属于对象头的,但是如下数组实验,类型并没有标记object header
实例数据不同的类型所占的空间不同:
64 位的JVM默认开启普通对象指针压缩 -XX:+UseCompressedOops (OOP即ordinary object pointer),由原8字节压缩到4字节大小
用于补齐对象内存长度的。因为JVM要求java代码的对象必须是8bit的倍数。如果一个对象用不到8N个字节则需要对其填充,以此来补齐对象头和实例数据占用内存之后剩余的空间大小。如果对象头和实例数据已经占满了JVM所分配的内存空间,那么就不用再进行对齐填充了。所有的对象分配的字节总SIZE需要是8的倍数,如果前面的对象头和实例数据占用的总SIZE不满足要求,则通过对齐数据来填满。
Idea 导入 pom依赖
org.openjdk.jol
jol-core
0.16
jol常用方法:
1.使用jol计算对象的大小(单位为字节):ClassLayout.parseInstance(obj).instanceSize()
2.使用jol查看对象内部的内存布局:ClassLayout.parseInstance(obj).toPrintable()
3.查看对象占用空间总大小:GraphLayout.parseInstance(obj).totalSize()
代码:
public class markword01_EmptyProperties {
public static void main(String[] args) {
System.out.println("空属性-对象布局================================");
markword01_EmptyProperties entity = new markword01_EmptyProperties();
// 打印java 对象内存布局
System.out.println(ClassLayout.parseInstance(entity).toPrintable());
}
}
输出结果:
com.tsj.threads.markword01_EmptyProperties object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
8 4 (object header: class) 0xf800c105
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
结果分析:
- OFF(OFFSET):偏移地址,单位字节;
- SZ(SIZE):占用的内存大小,单位为字节;
- TYPE DESCRIPTION:类型描述。
object header为对象头,object header 包含 mark + class;
gap字段,表示补齐padding。
- VALUE:对应内存中当前存储的值;
- Instance size:实例字节数值大小,
即:一个空的java对象(不包含任意字段属性)实例,其实例大小为16Byte。
怎么计算?mark + class + gap = 16.
- Space losses: 0 bytes internal + 4 bytes external
= 4 bytes total:表示内存补齐Padding,占用4个字节
代码:
public class markword01_EmptyArrayEntity {
public static void main(String[] args) {
System.out.println("数组布局================================");
markword01_EmptyArrayEntity[] entity = new markword01_EmptyArrayEntity[4];
// 打印java 对象内存布局
System.out.println(ClassLayout.parseInstance(entity).toPrintable());
}
}
输出结果:
[Lcom.tsj.threads.markword01_EmptyArrayEntity; object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
8 4 (object header: class) 0xf800c143
12 4 (array length) 4
12 4 (alignment/padding gap)
16 16 com.tsj.threads.markword01_EmptyArrayEntity markword01_EmptyArrayEntity;. N/A
Instance size: 32 bytes
Space losses: 4 bytes internal + 0 bytes external = 4 bytes total
结果分析:
- OFF:偏移地址,单位字节;
- SZ:占用的内存大小,单位为字节;
- TYPE DESCRIPTION:类型描述。
object header为对象头,object header 包含 mark + class;
gap字段,表示补齐padding。
array length:表示数组长度
- VALUE:对应内存中当前存储的值;
- Instance size:实例字节数值大小,
即:一个空的java数组,其实例大小为32Byte。
怎么计算?mark + class + array length + gap = 32.
- Space losses: 0 bytes internal + 4 bytes external
= 4 bytes total:表示内存补齐Padding,占用4个字节
public class Student {
private String name;
private Integer age;
}
public class StudentV1 {
private String nameV1;
private Integer ageV1;
private int sexV1;
}
public class StudentV2 {
private String nameV1;
private Integer ageV1;
private int sexV1;
private boolean live;
}
public static void main(String[] args) {
System.out.println("有属性-对象布局 1================================");
Student student = new Student();
System.out.println(ClassLayout.parseInstance(student).toPrintable());
System.out.println("有属性-对象布局 2================================");
StudentV1 studentV1 = new StudentV1();
System.out.println(ClassLayout.parseInstance(studentV1).toPrintable());
System.out.println("有属性-对象布局 3================================");
StudentV2 studentV2 = new StudentV2();
System.out.println(ClassLayout.parseInstance(studentV2).toPrintable());
}
有属性-对象布局 1================================
# WARNING: Unable to attach Serviceability Agent. You can try again with escalated privileges. Two options: a) use -Djol.tryWithSudo=true to try with sudo; b) echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
com.tsj.threads.Student object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
8 4 (object header: class) 0xf800c143
12 4 java.lang.String Student.name null
16 4 java.lang.Integer Student.age null
20 4 (object alignment gap)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
有属性-对象布局 2================================
com.tsj.threads.StudentV1 object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
8 4 (object header: class) 0xf800f359
12 4 int StudentV1.sexV1 0
16 4 java.lang.String StudentV1.nameV1 null
20 4 java.lang.Integer StudentV1.ageV1 null
Instance size: 24 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
有属性-对象布局 3================================
com.tsj.threads.StudentV2 object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
8 4 (object header: class) 0xf800f39e
12 4 int StudentV2.sexV1 0
16 1 boolean StudentV2.live false
17 3 (alignment/padding gap)
20 4 java.lang.String StudentV2.nameV1 null
24 4 java.lang.Integer StudentV2.ageV1 null
28 4 (object alignment gap)
Instance size: 32 bytes
Space losses: 3 bytes internal + 4 bytes external = 7 bytes total
- Student:
Instance size: 24 bytes;
24 bytes = mark(8) + class(4) + name(4,压缩) + age(4,压缩) + gap(4)
name 类型:String,age类型:Integer,都属于引用类型。JDK默认开启了压缩,8位压缩至4位。
进行了一次补齐,补齐4bytes
- StudentV1:
Instance size: 24 bytes
24 bytes = mark(8) + class(4) + sexV1(4) + nameV1(4,压缩) + ageV1(4,压缩)
nameV1 类型:String,ageV1类型:Integer,都属于引用类型。JDK默认开启了压缩,8位压缩至4位。另外,sexV1类型为int,基本类型无法压缩。
没有进行补齐
- StudentV2:
Instance size: 32 bytes
24 bytes = mark(8) + class(4) + sexV1(4) + live (1) + gap(3) + nameV1(4,压缩) + ageV1(4,压缩) + gap(4)
进行了两次补齐,一次为internal,为live补齐了3bities,一次为external,为整个对象补齐4字节。
主要是针对synchronized,JDK 1.6 优化了synchronized,提出了锁升级的机制,即无锁-偏向锁-轻量级锁-重量级锁,我这里不验证锁升级的过程,主要验证的数这四种锁在对象头是怎么存储和标识的。
|--------------------------------------------------------------------------------------------------------------|
| Object Header (128 bits) |
|--------------------------------------------------------------------------------------------------------------|
| Mark Word (64 bits) | Klass Word (64 bits) |
|--------------------------------------------------------------------------------------------------------------|
| unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:1 | lock:2 | OOP to metadata object | 无锁
|----------------------------------------------------------------------|--------|------------------------------|
| thread:54 | epoch:2 | unused:1 | age:4 | biased_lock:1 | lock:2 | OOP to metadata object | 偏向锁
|----------------------------------------------------------------------|--------|------------------------------|
| ptr_to_lock_record:62 | lock:2 | OOP to metadata object | 轻量锁
|----------------------------------------------------------------------|--------|------------------------------|
| ptr_to_heavyweight_monitor:62 | lock:2 | OOP to metadata object | 重量锁
|----------------------------------------------------------------------|--------|------------------------------|
| | lock:2 | OOP to metadata object | GC
|--------------------------------------------------------------------------------------------------------------|
lock: 锁状态标记位,该标记的值不同,整个mark word表示的含义不同。
biased_lock:偏向锁标记,为1时表示对象启用偏向锁,为0时表示对象没有偏向锁。
从分布可以得出,看锁标记,直接看后 3 位即可,如果给到你锁类型标识,你只需要看前后2位就行。
biased_lock | lock | 16进制 | 状态 |
---|---|---|---|
0 | 01 | 1 | 无锁 |
1 | 01 | 5 | 偏向 |
0 | 00 | 0 | 轻量 |
0 | 10 | 2 | 重量 |
0 | 11 | 3 | GC |
public class SyncTest03 {
public static void main(String[] args) throws InterruptedException {
Student a = new Student();
String str = ClassLayout.parseInstance(a).toPrintable();
System.out.println("未上锁,输出ClassLayout");
System.out.println(str);//01 无锁状态
new Thread() {
public void run() {
synchronized (a) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
Thread.sleep(10);
str = ClassLayout.parseInstance(a).toPrintable();
System.out.println("第一次上锁,输出ClassLayout");
System.out.println(str);//00 轻量级锁
new Thread() {
public void run() {
synchronized (a) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
Thread.sleep(10);
str = ClassLayout.parseInstance(a).toPrintable();
System.out.println("第二次上锁,输出ClassLayout");
System.out.println(str);//10 重量级锁
}
}
JDK默认开启偏向锁,但延时的,需要通过参数-XX:BiasedLockingStartupDelay=0禁用延时。(idea在VM Options设置就行了)
偏向锁的开启和禁止延迟参数:
// 关闭偏向锁延迟
-XX:BiasedLockingStartupDelay=0
// 偏向锁的关闭
-XX:+UseBiasedLocking
设置-XX:BiasedLockingStartupDelay=0
输出结果:
未上锁,输出ClassLayout
com.tsj.threads.Student object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000005 (biasable; age: 0)
8 4 (object header: class) 0xf800c143
12 4 java.lang.String Student.name null
16 4 java.lang.Integer Student.age null
20 4 (object alignment gap)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
第一次上锁,输出ClassLayout
com.tsj.threads.Student object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x00007fce8f96e005 (biased: 0x0000001ff3a3e5b8; epoch: 0; age: 0)
8 4 (object header: class) 0xf800c143
12 4 java.lang.String Student.name null
16 4 java.lang.Integer Student.age null
20 4 (object alignment gap)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
第二次上锁,输出ClassLayout
com.tsj.threads.Student object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x00007fce9801128a (fat lock: 0x00007fce9801128a)
8 4 (object header: class) 0xf800c143
12 4 java.lang.String Student.name null
16 4 java.lang.Integer Student.age null
20 4 (object alignment gap)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
结果分析:
上锁前
mark标识:0x0000000000000005 (biasable; age: 0)
(1)biasable; age: 0
biasable:无锁可偏向
age:年龄为0
(2)0x0000000000000005
当前对象头为0x0000000000000005,二级制为:101
第一次上锁
mark标识:0x00007fce8f96e005 (biased: 0x0000001ff3a3e5b8; epoch: 0; age: 0)
(1)biased: 0x0000001ff3a3e5b8; epoch: 0; age: 0
biased:表示偏向锁
0x0000001ff3a3e5b8,二进制为:1111111110011101000111110010110111000
(2)0x00007fce8f96e005
当前对象头为0x00007fce8f96e005,二进制为:11111111100111010001111100101101110000000000101
第二次上锁
mark标识:0x00007fce9801128a (fat lock: 0x00007fce9801128a)
(1)fat lock: 0x00007fce9801128a
fat lock :表示重量级锁
0x00007fce9801128a ,表示上锁对象头,二进制为:11111111100111010011000000000010001001010001010,查看后两位为10,10表示重量级锁
(2)0x00007fce9801128a
表示当前对象头为0x00007fce9801128a,二进制为:11111111100111010011000000000010001001010001010
加锁前对象处于无锁状态,加锁中处于偏向锁,同一时间再次访问同一锁后,偏向锁膨胀为重量级锁
注意,在偏向锁状态,当前对象头和上锁的对象头往往不是同一个。
设置-XX:+UseBiasedLocking
输出结果:
未上锁,输出ClassLayout
com.tsj.threads.Student object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
8 4 (object header: class) 0xf800c143
12 4 java.lang.String Student.name null
16 4 java.lang.Integer Student.age null
20 4 (object alignment gap)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
第一次上锁,输出ClassLayout
com.tsj.threads.Student object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000700008ebc9b0 (thin lock: 0x0000700008ebc9b0)
8 4 (object header: class) 0xf800c143
12 4 java.lang.String Student.name null
16 4 java.lang.Integer Student.age null
20 4 (object alignment gap)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
第二次上锁,输出ClassLayout
com.tsj.threads.Student object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x00007f7bd001f13a (fat lock: 0x00007f7bd001f13a)
8 4 (object header: class) 0xf800c143
12 4 java.lang.String Student.name null
16 4 java.lang.Integer Student.age null
20 4 (object alignment gap)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
结果分析:
未上锁:
mark标识:0x0000000000000001 (non-biasable; age: 0)
(1)non-biasable; age: 0
non-biasable:无锁
第一次加锁,是真正加上锁
mark标识:0x0000700008ebc9b0 (thin lock: 0x0000700008ebc9b0)
(1)thin lock: 0x0000700008ebc9b0
thin lock :轻量级锁
0x0000700008ebc9b0,表示上锁的对象头,二进制为:11100000000000000001000111010111100100110110000,看后两位表示:00,00:表示轻量级锁
(2)0x0000700008ebc9b0
对象头是:0x0000700008ebc9b0
第二次还是对同一把锁方法,存在同一时间访问同一锁的情况,就会导致轻量级锁膨胀为重量级锁。
mark标识为:0x00007f7bd001f13a (fat lock: 0x00007f7bd001f13a)
(1)fat lock: 0x00007f7bd001f13a
fat lock :表示重量级锁
0x00007f7bd001f13a,上锁的对象头,二进制为:11111110111101111010000000000011111000100111010,看后两位表示:01,01:表示轻量级锁
(2)0x00007f7bd001f13a
对象头是0x00007f7bd001f13a
加锁前对象处于无锁状态,加锁中处于轻量锁状态,同一时间再次访问同一锁后,轻量级锁膨胀为重量级锁