java打印对象头信息_盘一盘 synchronized (一)—— 从打印Java对象头说起

本文通过解析Java对象头的结构,详细介绍了Mark Word和Klass Word的各个字段,如锁状态标记位、偏向锁、无锁、轻量级锁和重量级锁的状态转换。通过实例展示了如何使用JOL工具打印对象头信息,分析了不同锁状态下的对象头变化,帮助理解Java并发中的锁机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

|--------------------------------------------------------------------------------------------------------------|

| 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时表示对象没有偏向锁。

b0c88f55f62ba40231189dca23f00149.png

age:Java GC标记位对象年龄。

identity_hashcode:对象标识Hash码,采用延迟加载技术。当对象使用HashCode()计算后,并会将结果写到该对象头中。当对象被锁定时,该值会移动到线程Monitor中。

thread:持有偏向锁的线程ID和其他信息。这个线程ID并不是JVM分配的线程ID号,和Java Thread中的ID是两个概念。

epoch:偏向时间戳。

ptr_to_lock_record:指向栈中锁记录的指针。

ptr_to_heavyweight_monitor:指向线程Monitor的指针。

使用JOL工具类,打印对象头

使用maven的方式,添加jol依赖

org.openjdk.jol

jol-core

0.8

创建一个对象A

public classA {boolean flag = false;

}

使用jol工具类输出A对象的对象头

public static voidmain(String[] args){

A a= newA();

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

}

看看输出结果

5603385b6c5842265f88d98310de5872.png

输出的第一行内容和锁状态内容对应

unused:1 | age:4 | biased_lock:1 | lock:2

0           0000             0                01     代表A对象正处于无锁状态

第三行中表示的是被指针压缩为32位的klass pointer

第四行则是我们创建的A对象属性信息 1字节的boolean值

第五行则代表了对象的对齐字段 为了凑齐64位的对象,对齐字段占用了3个字节,24bit

偏向锁

public static void main(String[] args) throwsInterruptedException {

Thread.sleep(5000);

A a= newA();

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

}

3292811

3292811输出结果

d7c3507cab8c84a39aecb48d135fadeb.png

刚开始使用这段代码我是震惊的,为什么睡眠了5s中就把活生生的A对象由无锁状态改变成为偏向锁了呢?别急,容我慢慢道来!

JVM启动时会进行一系列的复杂活动,比如装载配置,系统类初始化等等。在这个过程中会使用大量synchronized关键字对对象加锁,且这些锁大多数都不是偏向锁。为了减少初始化时间,JVM默认延时加载偏向锁。这个延时的时间大概为4s左右,具体时间因机器而异。当然我们也可以设置JVM参数 -XX:BiasedLockingStartupDelay=0 来取消延时加载偏向锁。

可能你又要问了,我这也没使用synchronized关键字呀,那不也应该是无锁么?怎么会是偏向锁呢?

仔细看一下偏向锁的组成,对照输出结果红色划线位置,你会发现占用 thread 和 epoch 的 位置的均为0,说明当前偏向锁并没有偏向任何线程。此时这个偏向锁正处于可偏向状态,准备好进行偏向了!你也可以理解为此时的偏向锁是一个

特殊状态的无锁。

70d8741929e902f17d99c3e56e00b8fa.png

大家可以看下面这张图理解一下对象头的状态的创建过程

98fa2bd639411285171aacb1547f4495.png

再来看看这段代码,使用了synchronized关键字

public static void main(String[] args) throwsInterruptedException {

Thread.sleep(5000);

A a= newA();synchronized(a){

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

}

}

此时对象a,对象头内容有了明显的变化,当前偏向锁偏向主线程。

c2c115c40765d38360230f9412798147.png

轻量级锁

public static void main(String[] args) throwsInterruptedException {

Thread.sleep(5000);

A a= newA();

Thread thread1= newThread(){

@Overridepublic voidrun() {synchronized(a){

System.out.println("thread1 locking");

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

}try{//thread1退出同步代码块,且没有死亡

Thread.sleep(3000);

}catch(InterruptedException e) {

e.printStackTrace();

}

}

};

Thread thread2= newThread(){

@Overridepublic voidrun() {synchronized(a){

System.out.println("thread2 locking");

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

}

}

};

thread1.start();//让thread1执行完同步代码块中方法。

Thread.sleep(3000);

thread2.start();

}

thread1中依旧输出偏向锁,thread2获取对象A时,thread1已经退出同步代码块,故此时thread2输出结果为轻量级锁。

8bac3885a6577eeef3b2f0a12d8a8995.png

重量级锁

public static void main(String[] args) throwsInterruptedException {

Thread.sleep(5000);

A a= newA();

Thread thread1= newThread(){

@Overridepublic voidrun() {synchronized(a){

System.out.println("thread1 locking");

System.out.println(ClassLayout.parseInstance(a).toPrintable());try{//让线程晚点儿死亡,造成锁的竞争

Thread.sleep(2000);

}catch(InterruptedException e) {

e.printStackTrace();

}

}

}

};

Thread thread2= newThread(){

@Overridepublic voidrun() {synchronized(a){

System.out.println("thread2 locking");

System.out.println(ClassLayout.parseInstance(a).toPrintable());try{

Thread.sleep(2000);

}catch(InterruptedException e) {

e.printStackTrace();

}

}

}

};

thread1.start();

thread2.start();

}

thread1 和 thread2 同时竞争对象a,此时输出结果为重量级锁

aef27a3ef84e2b5790e8a9d77d3cb529.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值