了解Synchronized对象头?

1、对象头的结构

Java对象存储在内存中结构为:

  1. 对象头(Header):
  2. 实例数据(Instance Data):定义类中的成员属性
  3. 对齐填充字节(Padding):由于HotSpot虚拟机的自动内存管理系统要求对象的起始地址必须是8字节的整数倍,也就是对象的大小必须是8字节的整数倍。

Java对象头结构为:

  1. Mark Word :用于存储对象自身的运行时数据, 如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等
  2. Klass Pointer :对象指向它的类的元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例

那么我们本期先了解什么是对象头,对象头的结构又是如何的呢?
Java对象的对象头由 mark word 和 klass pointer 两部分组成,mark word存储了同步状态、标识、hashcode、GC状态等等。klass pointer存储对象的类型指针,该指针指向它的类元数据值得注意的是,如果应用的对象过多,使用64位的指针将浪费大量内存。64位的JVM比32位的JVM多耗费50%的内存。

1.1、Mark Word

这部分主要用来存储对象自身的运行时数据,如hashcode、gc分代年龄等。mark word的位长度为JVM的一个Word大小,也就是说32位JVM的Mark word为32位,64位JVM为64位。

enum {  locked_value             = 0, // 0 00 轻量级锁unlocked_value           = 1, // 0 01 无锁monitor_value            = 2, // 0 10 重量级锁marked_value             = 3, // 0 11 gc标志biased_lock_pattern      = 5  // 1 01 偏向锁};

在这里插入图片描述
markword在不同的场景下会有不同的bit结构如下:

  1. 在正常不加锁时,mark word 由lock、biased_lock、age、identity_hashcode组成,age是GC的年龄,最大15(4位),每从Survivor区复制一次,年龄增加1。identity_hashcode就是对象的哈希码,当对象处于加锁状态时,这个哈希码会移到monitor,(synchronized会在代码块前后插入monitor)。
  2. 在偏向锁时,mark word 由lock、biased_lock、age、epoch、thread组成。epoch:偏向锁在CAS锁操作过程中,偏向性标识,表示对象更偏向哪个锁。thread:持有偏向锁的线程ID,如果该线程再次访问这个锁的代码块,可以直接访问。
  3. 在轻量级锁时,mark word 由lock、ptr_to_lock_record组成。ptr_to_lock_record:指向栈中锁记录的指针。
  4. 在重量级锁时,mark word 由lock、ptr_to_heavyweight_monitor组成。ptr_to_heavyweight_monitor:指向对象监视器Monitor的指针。

1.2、Klass Pointer

即对象指向它的元数据的指针,虚拟机通过这个指针来确定是哪个类的实例。并不是所有的虚拟机实现都必须在对象数据上保留类型指针(通过句柄池访问)。
Class对象的类型指针,JDK8默认开启指针压缩后为4字节,关闭指针压缩(-XX:-UseCompressedOops)后,长度为8字节。其指向的位置是对象对应的Class对象(其对应的元数据对象)的内存地址。

  1. 句柄访问:会在堆中开辟一块内存作为句柄池,句柄中储存了对象实例数据(属性值结构体)的内存地址,访问类型数据的内存地址(类信息,方法类型信息),对象实例数据一般也在heap中开辟,类型数据一般储存在方法区中。reference存储的是稳定的句柄地址,对象移动也不会影响句柄的地址位置。
  2. 指针访问:指针访问方式指reference中直接储存对象在heap中的内存地址,但对应的类型数据访问地址需要在实例中存储。节省了一次指针定位的开销。但是对象移动后需要改变reference的地址。

1.3、术语介绍

我们现在使用的64位 JVM会默认使用选项 +UseCompressedOops 开启指针压缩,将指针压缩至32位。

|--------------------------------------------------------------------------------------------------------------|
|                                              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的指针。

2、对象头打印

2.1、工具依赖包

<dependency><groupId>org.openjdk.jol</groupId><artifactId>jol-core</artifactId><version>0.8</version>
</dependency>

2.2、未加锁

package cn.wen.sync;import org.openjdk.jol.info.ClassLayout;public class A {boolean flag = false;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

2.3、偏向锁

package cn.wen.sync;import org.openjdk.jol.info.ClassLayout;/*** 对偏向锁的对象头进行查看*/
public class BiasedLockDemo1 {public static void main(String[] args) {try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}A a = new A();System.out.println(ClassLayout.parseInstance(a).toPrintable());}}

输出结果:
在这里插入图片描述

因为 lock 01 为无锁状态 biased_lock 为 1 则为偏向锁
刚开始使用这段代码我是震惊的,为什么睡眠了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());}
}

在这里插入图片描述
thread 应该是线程的地址 当前偏向锁偏向了主线程。

2.4、轻量级锁

package cn.wen.sync;import org.openjdk.jol.info.ClassLayout;/*** 轻量级锁的展示*/
public class CASLockDemo1 {public static void main(String[] args) throws Exception {Thread.sleep(5000);A a = new A();Thread thread1= new Thread(){@Overridepublic void run() {synchronized (a){System.out.println("thread1 locking");System.out.println(ClassLayout.parseInstance(a).toPrintable()); // 偏向锁}}};thread1.start();thread1.join();Thread.sleep(10000);synchronized (a){System.out.println("main locking");System.out.println(ClassLayout.parseInstance(a).toPrintable());// 轻量锁}}
}

thread1中依旧输出偏向锁,主线程获取对象A时,thread1虽然已经退出同步代码块,但主线程和thread1仍然为锁的交替竞争关系。故此时主线程输出结果为轻量级锁。
在这里插入图片描述

2.5、轻量级锁

public static void main(String[] args) throws InterruptedException {Thread.sleep(5000);A a = new A();Thread thread1 = new Thread(){@Overridepublic 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(){@Overridepublic 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,此时输出结果为重量级锁
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/25174.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Web Dart前端:探索、挑战与未来展望

Web Dart前端&#xff1a;探索、挑战与未来展望 在数字化浪潮的推动下&#xff0c;Web前端技术日新月异&#xff0c;其中Dart语言作为一种高效且灵活的编程语言&#xff0c;正逐渐在Web前端领域崭露头角。然而&#xff0c;Dart在Web前端的应用仍面临诸多挑战和未知。本文将从四…

Linux--进程间通信(system V共享内存)

目录 1.原理部分 2.系统调用接口 参数说明 返回值 1. 函数原型 2. 参数说明 3. 返回值 4. 原理 5. 注意事项 3.使用一下shmget&#xff08;一段代码&#xff09; 4.一个案例&#xff08;一段代码) 1.简单封装一下 2.使用共享内存 2.1挂接&#xff08;shmat&#x…

Java 语言概述 -- Java 语言的介绍、现在、过去与将来

大家好&#xff0c;我是栗筝i&#xff0c;这篇文章是我的 “栗筝i 的 Java 技术栈” 专栏的第 001 篇文章&#xff0c;在 “栗筝i 的 Java 技术栈” 这个专栏中我会持续为大家更新 Java 技术相关全套技术栈内容。专栏的主要目标是已经有一定 Java 开发经验&#xff0c;并希望进…

直接赋值导致响应式断开,前端深浅拷贝

title: 直接赋值导致响应式断开&#xff0c;前端深浅拷贝 date: 2024-06-08 09:56:05 tags: vue3 vue3中的ref对象的深浅拷贝问题&#xff0c;实际应用出现的相关的问题。 概念总述 浅拷贝 仅仅复制了数据&#xff0c;没有改变他原来的引用。 表现&#xff1a;当你对新对象…

Unity3D EventMgr事件订阅与发布详解

在游戏开发过程中&#xff0c;经常需要处理各种事件&#xff0c;比如角色的移动、碰撞、攻击等。为了更好地管理和处理这些事件&#xff0c;Unity3D提供了EventMgr事件订阅与发布机制&#xff0c;通过该机制可以实现不同对象之间的事件通信&#xff0c;让游戏逻辑更加清晰和灵活…

LLVM Cpu0 新后端7 第一部分 DAG调试 dot文件 Machine Pass

想好好熟悉一下llvm开发一个新后端都要干什么&#xff0c;于是参考了老师的系列文章&#xff1a; LLVM 后端实践笔记 代码在这里&#xff08;还没来得及准备&#xff0c;先用网盘暂存一下&#xff09;&#xff1a; 链接: https://pan.baidu.com/s/1V_tZkt9uvxo5bnUufhMQ_Q?…

单线服务器与双线服务器的区别?

单线服务器和双线服务器之间有什么区别呢&#xff1f;接下来就让小万来为大家具体分析一下吧&#xff01; 首先单线服务器和双线服务器之间运营商的性质是不同的&#xff0c;单线服务器主要是一家带宽运营商&#xff0c;而双线服务器则是有两家运营商提供带宽的线路。 单线服务…

spring两种代理方式

Spring 提供了两种主要的代理&#xff08;proxy&#xff09;方式&#xff0c;分别是基于JDK动态代理和基于CGLIB的代理。这两种代理方式各有其特点和适用场景。 1. JDK动态代理 特点&#xff1a; - 基于Java的接口&#xff08;Interface&#xff09;。 - 代理类必须实现一个或…

contos7使用docker安装vulhub

contos7下使用docker安装vulhub 1. 安装docker 1. 更新yum &#xff08;1&#xff09;切换root用户 su root &#xff08;2&#xff09;更新yum yum update 2. 卸载旧版本的docker sudo yum remove docker sudo yum remove docker-client sudo yum remove docker-clien…

【个人博客搭建】(21)使用AutoMap对象映射

在.NET WebAPI项目中&#xff0c;使用AutoMap进行对象映射是一种高效的数据处理方式。通过自动映射机制&#xff0c;可以极大地简化对象之间的转换过程&#xff0c;提高代码的可维护性和整洁性。下面将详细探讨如何在.NET WebAPI中使用AutoMap进行对象映射&#xff1a; 安装和配…

高并发ping多台主机IP

简介 社区或者是大型公司往往有成千上万或者几百台设备&#xff0c;保持设备始终在线对网络运维人员来说至关重要&#xff0c;然而一个一个登录检查&#xff0c;或者一个一个ping并不明智&#xff0c;累人且效率极低&#xff0c;并出错率高。花钱买检测服务当我没说。 shell编…

K210视觉识别模块学习笔记5:(嘉楠)训练使用模型_识别人脸

今日开始学习K210视觉识别模块:(嘉楠)训练与使用模型_识别人脸 亚博智能的K210视觉识别模块...... 固件库版本: canmv_yahboom_v2.1.1.bin 之前的训练网址部署模型时需要我们自己更换固件&#xff0c;而且还不能用亚博的图像操作库函数了&#xff0c;这十分不友好&#xff0…

Web前端从什么学起:探索前端世界的起点与路径

Web前端从什么学起&#xff1a;探索前端世界的起点与路径 在数字化浪潮席卷而来的今天&#xff0c;Web前端技术作为构建用户界面的核心力量&#xff0c;日益受到人们的关注。对于初学者来说&#xff0c;Web前端的学习旅程可能充满了未知与挑战。那么&#xff0c;Web前端究竟应…

liunx查看日志

tail查看日志 tail 查看文件的末尾部分 -f 实时监控日志文件的更新&#xff0c;如果有新的日志将会实时显示 -n 查看日志的后n行 tail -fn 100 filename.log // 实时查看filename.log的最后100行head查看日志 head 查看日志的头部 -n 指定查看行数 head -n 100 filename.log…

vue 使用 Vxe UI vxe-print 实现复杂的 Web 打印,支持页眉、页尾、分页的自定义模板

Vxe UI vue 使用 Vxe UI vxe-print 实现复杂的 Web 打印&#xff0c;支持页眉、页尾、分页的自定义模板 官方文档 https://vxeui.com 查看 github、gitee 页眉-自定义标题 说明&#xff1a;vxe-print-page-break标签用于定义分页&#xff0c;一个标签一页内容&#xff0c;超…

c语言基础篇B

B1.数据的输入与输出 c语言本身不提供输入输出语句&#xff0c;输入和输出操作是由c函数库中的函数来实现的在使用系统库函数时&#xff0c;要用预编译命令“#include”将有关的“头文件”包括到用户源文件中 include"stdio.h"或者include B2.printf()函数&#x…

Python怎么分开画图:深入探索与实战应用

Python怎么分开画图&#xff1a;深入探索与实战应用 在Python的数据可视化领域&#xff0c;分开画图是一项至关重要的技能。它能够帮助我们更清晰、更有条理地展示数据&#xff0c;进而发现数据中的规律和趋势。本文将从四个方面、五个方面、六个方面和七个方面&#xff0c;详…

YOLOv5改进 | 主干网络 | 用SimRepCSP作为主干网络提取特征【全网独家 + 降本增效】

&#x1f4a1;&#x1f4a1;&#x1f4a1;本专栏所有程序均经过测试&#xff0c;可成功执行&#x1f4a1;&#x1f4a1;&#x1f4a1; SimRepCSP 类似于 YOLOv7的主干网络&#xff0c;由卷积模块和重参数化卷积&#xff08;RepConv&#xff09;模块组合而成&#xff0c;以 Cro…

WPF Command 的使用

一、Command类的创建 >> 构造函数方法中传入了一个委托 public class MyCommand : ICommand { public readonly Action _action; public MyCommand(Action action) { this._action action; } public event EventHandler CanExecuteChanged;…

学习使用 Frida 过程中出现的问题

一、adb shell命令报错&#xff1a;error: no devices found 目前该问题解决方法仅供参考&#xff0c;可先看看再选择试试&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 查看此电脑也会发现没有出现手机型号文件夹。 第一步&#xff1a; 检查一下手机开了u…