了解Synchronized对象头?

news2025/1/17 15:32:24

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,此时输出结果为重量级锁
在这里插入图片描述

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

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

相关文章

高通SDX12:Voice Over USB 功能调试

一、功能概述及使用环境 Linux PC 作为上位机,内置 SLIC基于高通 SDX12 平台的设备作为从设备,通过USB连接到 Linux PC 上,在 PC 上枚举 UAC 设备从设备进行 MO/MT Call 时,上位机使用 arecord 进行录音,音频数据通过 USB 传至上位机,上位机停止录音后再使用 aplay 进行播…

经典文献阅读之--Online Monocular Lane Mapping(使用Catmull-Rom样条曲线完成在线单目车道建图)

0. 简介 对于单目摄像头完成SLAM建图这类操作&#xff0c;对于自动驾驶行业非常重要&#xff0c;《Online Monocular Lane Mapping Using Catmull-Rom Spline》介绍了一种仅依靠单个摄像头和里程计生成基于样条的在线单目车道建图方法。我们提出的技术将车道关联过程建模为一个…

【STM32】ucOS-III多任务程序

【STM32】uc/OS-III多任务程序 文章目录 【STM32】uc/OS-III多任务程序STM32F103C8T6移植uC/OS-III基于HAL库超完整详细过程与相关实验实验任务实验过程一、 uC/OS-III源码下载二、 建立STM32CubeMX工程三、 复制uC/OS-III文件到工程文件夹四、 添加工程组件和头文件路径五、修…

【中颖】SH79F9202 串口通信

头文件 uart.h #ifndef UART_H #define UART_H#include "SH79F9202.h" #include "LCD.h" #include "timer2.h" #include "timer5.h" #include "cpu.h" #include "key.h" #include "io.h" #include &qu…

【C++】深入理解decltype和decltype(auto)

深入理解decltype和decltype&#xff08;auto&#xff09; 一、decltype语法介绍二、decltype的推导规则1. expr不加括号2. expr加上括号 三、关于decltype的CV属性推导四、 decltype(auto) 的使用 一、decltype语法介绍 decltype关键字是C11新标准引入的关键字&#xff0c;它…

向量数据库是什么?

向量数据库是什么&#xff1f; 随着人工智能和机器学习技术的迅猛发展&#xff0c;向量数据库作为一种新型数据库引起了广泛关注。向量数据库专门用于存储和查询高维向量数据&#xff0c;是在大规模数据检索和相似性搜索领域的重要工具。 向量数据库的定义 向量数据库是一种…

心链13---主页切换功能 + loading特效 + 导航栏完善 + 队伍页接口修改

心链 — 伙伴匹配系统 直接取出所有用户&#xff0c;依次和当前用户计算分数&#xff0c;取 TOP N&#xff08;54 秒&#xff09; 优化方法&#xff1a; 切忌不要在数据量大的时候循环输出日志&#xff08;取消掉日志后 20 秒&#xff09;Map 存了所有的分数信息&#xff0c;占…

上位机图像处理和嵌入式模块部署(f407 mcu和其他mcu品类的选择)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 很多朋友读书的时候学的是stm32&#xff0c;工作中用的也是stm32。这本来问题不大&#xff0c;但是过去两三年的经历告诉我们&#xff0c;mcu的使用…

Polar Web【中等】反序列化

Polar Web【中等】反序列化 Contents Polar Web【中等】反序列化思路&探索EXPPHP生成PayloadGET传递参数 运行&总结 思路&探索 一个经典的反序列化问题&#xff0c;本文采用PHP代码辅助生成序列字符串的方式生成 Payload 来进行手动渗透。 打开站点&#xff0c;分析…

Python编程基础4

模块&#xff1a;模块支持从逻辑上组织Python代码&#xff0c;当代码量变得非常大的时候&#xff0c;最好把代码分成一些有组织的代码段。代码片段相互间有一定的联系&#xff0c;可能是一个包含数据成员和方法的类、函数、变量。 搜索路径&#xff1a;模块的导入需要一个叫做‘…

构建智能汽车新质生产力丨美格智能亮相2024高通汽车技术与合作峰会

近日&#xff0c;以“我们一起&#xff0c;驭风前行”为主题的2024高通汽车技术与合作峰会在无锡国际会议中心隆重举行。作为高通公司的战略合作伙伴&#xff0c;美格智能受邀全程参与此次汽车技术与合作峰会。在峰会现场&#xff0c;美格智能产品团队隆重展示了多款基于高通平…

Wireshark自定义Lua插件

背景&#xff1a; 常见的抓包工具有tcpdump和wireshark&#xff0c;二者可基于网卡进行抓包&#xff1a;tcpdump用于Linux环境抓包&#xff0c;而wireshark用于windows环境。抓包后需借助包分析工具对数据进行解析&#xff0c;将不可读的二进制数转换为可读的数据结构。 wires…

VUE封装-自定义权限控制指令

在实际开发中&#xff0c;会遇到很多的权限控制、资源位的场景&#xff0c;其实就是用来控制某个组件的展示与否&#xff0c;可以是一个按钮、一个报表、一个TAB页面等 例如下图&#xff0c;我想通过当前登录的用户控制谷歌的这个logo显示与否 因为设计到的权限、资源位控制比…

摆脱Jenkins - 使用google cloudbuild 部署 java service 到 compute engine VM

在之前 介绍 cloud build 的文章中 初探 Google 云原生的CICD - CloudBuild 已经介绍过&#xff0c; 用cloud build 去部署1个 spring boot service 到 cloud run 是很简单的&#xff0c; 因为部署cloud run 无非就是用gcloud 去部署1个 GAR 上的docker image 到cloud run 容…

GUI编程-01

组件 窗口 弹窗 面板 文本框 列表框 按钮 图片 监听事件 鼠标 键盘事件 破解工具 Java提供了丰富的图形用户界面&#xff08;Graphics User Interface&#xff0c;GUI&#xff09;的类库&#xff0c;基于这些类库可以编写窗口程序。 Java关于图形界面的类库主要放在…

【Redis学习笔记05】Jedis客户端(string、list、set)

Jedis客户端 1. 命令 1.1 String类型 1.1.1 常见命令 SET命令 语法&#xff1a;SET key value [EX seconds | PX milliseconds] [NX|XX] 说明&#xff1a;将string类型的value值设置到指定key中&#xff0c;如果之前该key存在&#xff0c;则会覆盖原先的值&#xff0c;原先…

数染色体 算法 python源码

效果图如下&#xff1a; 原图&#xff1a; 完整代码&#xff1a; import cv2 import numpy as np from skimage import measure import randomimage cv2.imread(113.jpg, cv2.IMREAD_GRAYSCALE)blurred_img cv2.GaussianBlur(image, (5, 5), 0)_, binary_image cv2.thresho…

LibreOffice电子表格如何实现快速筛选并将结果放到新的工作表

如果是在excel或者wps中&#xff0c;可能大家都习惯了自动筛选&#xff0c;然后复制到新的工作表或者删除掉复制内容的办法。但是在LibreOffice中&#xff0c;经测试&#xff0c;大数据表的删除或者复制是非常慢的。这也是很多人放弃LibreOffice的原因之一。那么我们如何快速筛…

Rust 实战丨SSE(Server-Sent Events)

&#x1f4cc; SSE&#xff08;Server-Sent Events&#xff09;是一种允许服务器向客户端浏览器推送信息的技术。它是 HTML5 的一部分&#xff0c;专门用于建立一个单向的从服务器到客户端的通信连接。SSE的使用场景非常广泛&#xff0c;包括实时消息推送、实时通知更新等。 S…

html+CSS+js部分基础运用18

1. 按键修饰符的应用。①姓名&#xff1a;按下回车键时调用方法输出“姓名-密码”&#xff1b;②密码&#xff1a;按下shift回车时调用方法输出“姓名密码” 图1 初始效果图 图2 按键修饰符效果图 2. 仿淘宝Tab栏切换&#xff0c;熟悉…