盘一盘 synchronized (一)—— 从打印Java对象头说起
2021/5/17 14:55:22
本文主要是介绍盘一盘 synchronized (一)—— 从打印Java对象头说起,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
Java对象头的组成
Java对象的对象头由 mark word 和 klass pointer 两部分组成,
mark word存储了同步状态、标识、hashcode、GC状态等等。
klass pointer存储对象的类型指针,该指针指向它的类元数据
值得注意的是,如果应用的对象过多,使用64位的指针将浪费大量内存。64位的JVM比32位的JVM多耗费50%的内存。
我们现在使用的64位 JVM会默认使用选项 +UseCompressedOops 开启指针压缩,将指针压缩至32位。
以64位操作系统为例,对象头存储内容图例。
|--------------------------------------------------------------------------------------------------------------| | 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时表示对象没有偏向锁。
- 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依赖
<dependency> <groupId>org.openjdk.jol</groupId> <artifactId>jol-core</artifactId> <version>0.8</version> </dependency>
创建一个对象A
public class A { boolean flag = false; }
使用jol工具类输出A对象的对象头
public static void main(String[] args){ A a = new A(); System.out.println(ClassLayout.parseInstance(a).toPrintable()); }
看看输出结果
输出的第一行内容和锁状态内容对应
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) throws InterruptedException { Thread.sleep(5000); A a = new A(); System.out.println(ClassLayout.parseInstance(a).toPrintable()); }
输出结果
刚开始使用这段代码我是震惊的,为什么睡眠了5s中就把活生生的A对象由无锁状态改变成为偏向锁了呢?别急,容我慢慢道来!
JVM启动时会进行一系列的复杂活动,比如装载配置,系统类初始化等等。在这个过程中会使用大量synchronized关键字对对象加锁,且这些锁大多数都不是偏向锁。为了减少初始化时间,JVM默认延时加载偏向锁。这个延时的时间大概为4s左右,具体时间因机器而异。当然我们也可以设置JVM参数 -XX:BiasedLockingStartupDelay=0 来取消延时加载偏向锁。
可能你又要问了,我这也没使用synchronized关键字呀,那不也应该是无锁么?怎么会是偏向锁呢?
仔细看一下偏向锁的组成,对照输出结果红色划线位置,你会发现占用 thread 和 epoch 的 位置的均为0,说明当前偏向锁并没有偏向任何线程。此时这个偏向锁正处于可偏向状态,准备好进行偏向了!你也可以理解为此时的偏向锁是一个特殊状态的无锁。
大家可以看下面这张图理解一下对象头的状态的创建过程
再来看看这段代码,使用了synchronized关键字
public static void main(String[] args) throws InterruptedException { Thread.sleep(5000); A a = new A(); synchronized (a){ System.out.println(ClassLayout.parseInstance(a).toPrintable()); } }
轻量级锁
public static void main(String[] args) throws Exception { Thread.sleep(5000); A a = new A(); Thread thread1= new Thread(){ @Override public void run() { synchronized (a){ System.out.println("thread1 locking"); out.println(ClassLayout.parseInstance(a).toPrintable()); //偏向锁 } } }; thread1.start(); thread1.join(); Thread.sleep(10000); synchronized (a){ out.println("main locking"); out.println(ClassLayout.parseInstance(a).toPrintable());//轻量锁 } }
thread1中依旧输出偏向锁,主线程获取对象A时,thread1虽然已经退出同步代码块,但主线程和thread1仍然为锁的交替竞争关系。故此时主线程输出结果为轻量级锁。
重量级锁
public static void main(String[] args) throws InterruptedException { Thread.sleep(5000); A a = new A(); Thread thread1 = new Thread(){ @Override public void run() { 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 = new Thread(){ @Override public void run() { 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,此时输出结果为重量级锁
作者: 柠檬五个半
出处:https://www.cnblogs.com/LemonFive/p/11246086.html
版权:本站使用「CC BY 4.0」创作共享协议,转载请在文章明显位置注明作者及出处。
这篇关于盘一盘 synchronized (一)—— 从打印Java对象头说起的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-10-01基于Python+Vue开发的医院门诊预约挂号系统
- 2024-10-01基于Python+Vue开发的旅游景区管理系统
- 2024-10-01RestfulAPI入门指南:打造简单易懂的API接口
- 2024-10-01初学者指南:了解和使用Server Action
- 2024-10-01Server Component入门指南:搭建与配置详解
- 2024-10-01React 中使用 useRequest 实现数据请求
- 2024-10-01使用 golang 将ETH账户的资产平均分散到其他账户
- 2024-10-01JWT用户校验课程:从入门到实践
- 2024-10-01Server Component课程入门指南
- 2024-09-30Dnd-Kit学习:新手快速入门指南