JVM中对象头的方式有以下两种(以32位JVM为例)
|--------------------------------------------------------------|
| Object Header (64 bits) |
|------------------------------------|-------------------------|
| Mark Word (32 bits) | Klass Word (32 bits) |
|------------------------------------|-------------------------|
|---------------------------------------------------------------------------------|
| Object Header (96 bits) |
|--------------------------------|-----------------------|------------------------|
| Mark Word(32bits) | Klass Word(32bits) | array length(32bits) |
|--------------------------------|-----------------------|------------------------|
这部分主要用来存储对象自身的运行时数据,如hashcode、gc分代年龄等。mark word的位长度为JVM的一个Word大小,也就是说32位JVM的Mark word为32位,64位JVM为64位。 为了让一个字大小存储更多的信息,JVM将字的最低两个位设置为标记位,不同标记位下的Mark Word示意如下:
|-------------------------------------------------------|--------------------|
| Mark Word (32 bits) | State |
|-------------------------------------------------------|--------------------|
| identity_hashcode:25 | age:4 | biased_lock:1 | lock:2 | Normal |
|-------------------------------------------------------|--------------------|
| thread:23 | epoch:2 | age:4 | biased_lock:1 | lock:2 | Biased |
|-------------------------------------------------------|--------------------|
| ptr_to_lock_record:30 | lock:2 | Lightweight Locked |
|-------------------------------------------------------|--------------------|
| ptr_to_heavyweight_monitor:30 | lock:2 | Heavyweight Locked |
|-------------------------------------------------------|--------------------|
| | lock:2 | Marked for GC |
|-------------------------------------------------------|--------------------|
|--------------------------------|-----------------------|------------------------|
| biased_lock | lock | state |
|--------------------------------|-----------------------|------------------------|
| 0 | 01 | 无锁 |
|--------------------------------|-----------------------|------------------------|
| 1 | 01 | 偏向锁 |
|--------------------------------|-----------------------|------------------------|
| | 00 | 轻量级锁 |
|--------------------------------|-----------------------|------------------------|
| | 10 | 重量级锁 |
|--------------------------------|-----------------------|------------------------|
| | 11 | GC |
|--------------------------------|-----------------------|------------------------|
这一部分用于存储对象的类型指针,该指针指向它的类元数据,JVM通过这个指针确定对象是哪个类的实例。该指针的位长度为JVM的一个字大小,即32位的JVM为32位,64位的JVM为64位。
如果应用的对象过多,使用64位的指针将浪费大量内存,统计而言,64位的JVM将会比32位的JVM多耗费50%的内存。为了节约内存可以使用选项+UseCompressedOops
开启指针压缩,其中,oop即ordinary object pointer普通对象指针。开启该选项后,下列指针将压缩至32位:
当然,也不是所有的指针都会压缩,一些特殊类型的指针JVM不会优化,比如指向PermGen的Class对象指针(JDK8中指向元空间的Class对象指针)、本地变量、堆栈元素、入参、返回值和NULL指针等。
如果对象是一个数组,那么对象头还需要有额外的空间用于存储数组的长度,这部分数据的长度也随着JVM架构的不同而不同:32位的JVM上,长度为32位;64位JVM则为64位。64位JVM如果开启+UseCompressedOops选项,该区域长度也将由64位压缩至32位。
注:以下测试我本机是64位,无锁:unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:1 | lock:2;偏向锁:thread:54 | epoch:2 | unused:1 | age:4 | biased_lock:1 | lock:2
引入打印工具
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.8</version>
<scope>test</scope>
</dependency>
代码:
static class A {
boolean flag = false;
}
public static void main(String[] args){
A a = new A();
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
输出结果:
...A 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) 93 c2 00 f8 (10010011 11000010 00000000 11111000) (-134167917)
12 1 boolean A.flag false
13 3 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
最后两位是lock01
,表示无锁或者是偏向锁。则倒数第三位是biased_lock0
,标识当前对象无锁。
00000 0 01
JVM启动时会进行一系列的复杂活动,比如装载配置,系统类初始化等等。在这个过程中会使用大量synchronized关键字对对象加锁,且这些锁大多数都不是偏向锁。为了减少初始化时间,JVM默认延时加载偏向锁。这个延时的时间大概为4s左右,具体时间因机器而异。当然我们也可以设置JVM参数 -XX:BiasedLockingStartupDelay=0 来取消延时加载偏向锁。
修改代码:
public static void main(String[] args) throws InterruptedException {
Thread.sleep(5000);
A a = new A();
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
输出结果:
...A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 93 c2 00 f8 (10010011 11000010 00000000 11111000) (-134167917)
12 1 boolean A.flag false
13 3 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
最后两位是lock01
,表示无锁或者是偏向锁。则倒数第三位是biased_lock1
,标识当前对象可偏向(Thread的值还是0)。
00000 0 01
前面说过,identity_hashcode对象标识Hash码,采用延迟加载技术。 修改代码
public static void main(String[] args) throws InterruptedException {
Thread.sleep(5000);
A a = new A();
System.out.println(System.identityHashCode(a));
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
输出结果:
777874839
A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 97 6d 5d (00000001 10010111 01101101 01011101) (1567463169)
4 4 (object header) 2e 00 00 00 (00101110 00000000 00000000 00000000) (46)
8 4 (object header) 93 c2 00 f8 (10010011 11000010 00000000 11111000) (-134167917)
12 1 boolean A.flag false
13 3 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
00000001
标识无锁状态 ,777874839
十进制转二进制结果是2e 5d 6d 97