了解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(){
            @Override
            public 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(){
        @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,此时输出结果为重量级锁
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

USB Type-C 和 USB供电数据和电源角色

USB Type-C 连接器生态系统随着现代平台和设备需求的变化而不断发展。 USB Type-C 连接器生态系统可满足现代平台和设备不断变化的需求&#xff0c;并且符合更小、更薄且更轻便的外形设计趋势。此外&#xff0c;针对 Type-C 连接器修改 USB PD 有助于满足高耗电应用的需求。 …

GDPU JavaWeb Ajax请求

异步请求可以提升用户体验并优化页面性能。 ajax登录 实现ajax异步登录。 注意&#xff0c;ajax用到了jQuery库&#xff0c;先下载好相应的js库&#xff0c;然后复制导入到工程的web目录下&#xff0c;最好与你的前端页面同一层级。然后编写时路径一定要找准&#xff0c;“pag…

STM32F103C8移植uCOSIII并以不同周期点亮两个LED灯(HAL库方式)【uCOS】【STM32开发板】【STM32CubeMX】

STM32F103C8移植uC/OSIII并以不同周期点亮两个LED灯&#xff08;HAL库方式&#xff09;【uC/OS】【STM32开发板】【STM32CubeMX】 实验说明 将嵌入式操作系统uC/OSIII移植到STM32F103C8上&#xff0c;构建两个任务&#xff0c;两个任务分别以1s和3s周期对LED进行点亮—熄灭的…

Android基本概念

Android发展历史 Android 是一个流行的移动操作系统&#xff0c;由 Google 开发并于 2008 年首次推出。 Android是基于Linux开发的移动设备操作系统。 在2005年被Google收购&#xff0c;2008年发布Android1.0&#xff0c;后续发布1.5/1.6 ------ 直到2021年发布Andriod12 。 …

什么是大型语言模型 ?

引言 在本文[1]中&#xff0c;我们将从高层次概述大型语言模型 (LLM) 的具体含义。 背景 2023年11月&#xff0c;我偶然间听闻了OpenAI的开发者大会&#xff0c;这个大会展示了人工智能领域的革命性进展&#xff0c;让我深深着迷。怀着对这一领域的浓厚兴趣&#xff0c;我加入了…

【Python】解决Python报错:ZeroDivisionError: division by zero

​​​​ 文章目录 引言1. 错误详解2. 常见的出错场景2.1 直接除零2.2 变量导致的间接除零 3. 解决方案3.1 检查除数3.2 使用异常处理 4. 预防措施4.1 数据验证4.2 编写防御性代码 结语 引言 在Python中&#xff0c;尝试将一个数字除以零时&#xff0c;会抛出ZeroDivisionErr…

重邮计算机网络803-(1)概述

目录 二.互联网概述 1.网络的网络 2.计算机网络的概念 3. 互联网发展的三个阶段 4.制订互联网的正式标准要经过以下的四个阶段 5.互联网的组成&#xff08;功能&#xff09; 6.互联网功能 7.互联网的组成&#xff08;物理&#xff09; 8. 互联网的边缘部分 9.两种通信…

串口屏在焊接机上的应用

在现代工业自动化浪潮中&#xff0c;焊接技术作为制造业的脊梁&#xff0c;其精准与效率直接关系到产品的质量和生产线的流畅性。而在这场技术的革新中&#xff0c;串口屏以其独特的优势&#xff0c;正成为焊接机领域的璀璨明星。今天&#xff0c;就让我们一同探索串口屏如何助…

手猫助手Agent技术探索总结

随着LLM的发展&#xff0c;ChatGPT能力不断增强&#xff0c;AI不断有新的概念提出&#xff0c;一种衍生类型的应用AI Agent也借着这股春风开启了一波话题热度&#xff0c;各种初创公司&#xff0c;包括Open AI内部也都在密切关注着AI Agent领域的变化。阿里集团内的AI团队也有很…

了解JVM中的Server和Client参数

了解JVM中的Server和Client参数 Java虚拟机&#xff08;Java Virtual Machine&#xff0c;JVM&#xff09;作为Java程序运行的核心&#xff0c;提供了多种参数来优化和调整程序的性能和行为。其中&#xff0c;-server和-client是两个重要的参数&#xff0c;分别用于配置JVM在服…

SMS - 基于阿里云实现手机短信验证码登录(无需备案,非测试)

目录 SMS 环境调试 从阿里云云市场中购买第三方短信服务 调试短信验证码功能 实战开发 封装组件 对外接口 调用演示 SMS 环境调试 从阿里云云市场中购买第三方短信服务 a&#xff09;进入阿里云首页&#xff0c;然后从云市场中找到 “短信” &#xff08;一定要从 云…

BUG解决: Zotero 文献GBT7714无法正常调用

1. 下载csl文件 网上有推荐直接下载现成版本的&#xff0c;比如参考资料【1】的蓝奏云文件&#xff0c;但是还是无法实现功能&#xff08;空文档中可以用了&#xff09;。 2. Github版本 也有说网盘版本和那个 Juris-M 的 CSL bug 太多的。 总结 后面发现&#xff0c;只需…

【原创】springboot+mysql农业园区管理系统设计与实现

个人主页&#xff1a;程序猿小小杨 个人简介&#xff1a;从事开发多年&#xff0c;Java、Php、Python、前端开发均有涉猎 博客内容&#xff1a;Java项目实战、项目演示、技术分享 文末有作者名片&#xff0c;希望和大家一起共同进步&#xff0c;你只管努力&#xff0c;剩下的交…

Go语言学习记录

GO语法学习之路 学习时间段2024-06-02学习记录安装&环境配置Go安装包内容统一入门姿势&#xff1a;hello world实现 Go语法初学Go 运行时&#xff08;runtime&#xff09;Go解释器 2024-06-03学习记录交叉编译要去linux下执行要去Mac下执行要去win下执行 参数声明常量 2024…

大语言模型的sft

https://zhuanlan.zhihu.com/p/692892489https://zhuanlan.zhihu.com/p/6928924891.常见的sft的开发流程 a.根据业务场景调整提示词;越详细越好,不要让模型理解歧义,拆分。 b.尝试闭源和开源,以评估LLM能够解决这类场景问题。 c.准备数据,包括多个子任务。 d.训练上线…

Inpaint9.1软件下载附加详细安装教程

软件简介: Inpaint 是个人开发者Max开发的图片处理软件&#xff0c;可以高效去除水印&#xff0c;修复照片等。使用方法和操作都很简单&#xff0c;非常适合不会PS等软件的小白用户。 安 装 包 获 取 地 址&#xff1a; Iinpaint win版&#xff1a;​​https://souurl.cn/b…

Java Web学习笔记22——前端工程化

实际的前端开发&#xff1a; 前端工程化&#xff1a;是指在企业级的前端项目开发中&#xff0c;把前端开发所需的工具、技术、流程、经验等进行规范化、标准化。 环境准备&#xff1a; vue-cli&#xff1a; 介绍&#xff1a;vue-cli是Vue官方提供的一个脚手架&#xff0c;用于…

项目管理--领导者vs管理者

项目管理领导者和管理者&#xff0c;虽然这两个角色在项目管理中都非常重要&#xff0c;但它们之间还是存在一些区别。首先&#xff0c;让我们来了解一下这两个角色的定义和职责。项目管理领导者是指那些能够激励团队成员&#xff0c;带领他们朝着共同目标前进的人。他们具备良…

Vue基础知识:插槽——默认插槽,插槽的后备内容,具名插槽,作用域插槽的认识与使用。(slot,#default,row的认识)

1.插槽的基本认识&#xff1a; 作用&#xff1a;让组件内部的一些结构支持自定义 插槽的分类&#xff1a; 1.默认插槽&#xff08;组件内只能定制一处结构&#xff09; 2.具名插槽&#xff08;组件内可以定制多次结构&#xff09; 简单而言&#xff1a;就是你希望封装一个…

YOLOv10开源,高效轻量实时端到端目标检测新标准,速度提升46%

前言 实时目标检测在自动驾驶、机器人导航、物体追踪等领域应用广泛&#xff0c;近年来&#xff0c;YOLO 系列模型凭借其高效的性能和实时性&#xff0c;成为了该领域的主流方法。但传统的 YOLO 模型通常采用非极大值抑制 (NMS) 进行后处理&#xff0c;这会增加推理延迟&#…