0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

详细介绍synchronized和Object的关键方法和虚拟机实现原理

Linux阅码场 来源:内核工匠 2023-03-13 10:06 次阅读

一、前言

编程过程中经常会遇到线程的同步问题,Java 中对同步问题的解决方案比较多(synchronized、JUC、原子操作、volatile、条件变量等),其中synchronized 最方便、简单易用,也是java 编程中使用最多的临界区保护方案。本文主要讲述对象锁的相关知识,详细介绍synchronized 和Object 的关键方法的虚拟机实现原理。

二、Java 对象锁的使用方式

2.1 实例方法的同步

0125b334-bfd4-11ed-bfe3-dac502259ad0.png

synchronized 修饰实例方法,该同步仅对当前对象的该方法起作用,同一时间只能有一个线程可以进入该对象的此方法。对于不同对象的此函数,无法做到互斥保护。

2.2 静态方法的同步

012e647a-bfd4-11ed-bfe3-dac502259ad0.png

synchronized 修饰静态方法,该同步对当前类对象的该方法起作用,同一时间只能有一个线程可以进入该方法。

2.3 代码块的同步

01357d14-bfd4-11ed-bfe3-dac502259ad0.png

在大多少情况下,并不需要对整个方法进行保护,当synchronized 修饰代码块时,该代码块的访问依赖于object 对象锁的互斥访问,同一时间只能有一个线程持有object 对象锁。

更准确的来讲,synchronized 关键字是依赖于对象锁而生效的,每个synchronized 同步块开始的地方都会生成monitor-enter obj指令,同步块结束的地方生成monitor-exit obj 的指令,其中obj 为用于控制互斥访问的对象。同一时间只能有一个线程持有obj 的对象锁。在2.1 中synchronized 依赖的是实列对象,2.2 中synchronized 依赖的是类对象,2.3 中synchronized 依赖的是object 对象。

当一个对象控制多个代码块时,多个代码块也是互斥访问,如下面代码:

013f31a6-bfd4-11ed-bfe3-dac502259ad0.png

代码块①和代码块② 虽然在两个函数中,但是synchronized 依赖的对象都为object,这两个代码块也是互斥访问。

2.4 Object wait() 和notify() 使用方法

Object 作为所有类的基类,都实现了object方法。典型的用法如下:

014808ee-bfd4-11ed-bfe3-dac502259ad0.png

thread 1持有object 对象锁,并调用object.wait() 方法后,则该线程进入WAITING状态,并释放object 对象锁,等待其它线程来唤醒它。

当thread 2 持有object 对象锁,并调用object.notify()方法后,唤醒thread 1,thread 1

重新获得object 对象锁继续执行。Object类方法说明:

0153dcbe-bfd4-11ed-bfe3-dac502259ad0.png

三、Android对象内存结构

3.1 对象内存结构

015b436e-bfd4-11ed-bfe3-dac502259ad0.png

一个类的实例对象内存主要由3部分组成:

1). 对象头:对象头包括kclass_和monitor_两个字段,其中kclass_ 存放指向类对象的指针,通过该指针可以找到该对象对应的类,monitor_ 用于存放对象运行时的标识数据,例如: GC 标志位、哈希码、锁状态等信息,后面详细分析。

2). 实例数据,该部分存放实例变量值,父类实例变量值在前,子类在后,且实例变量值按照如下顺序进行排序:

01641c32-bfd4-11ed-bfe3-dac502259ad0.png

3).对齐填充,对象在内存中是按照8byte 对齐的,如果实例数据部分没有按照8byte对齐,则填充为8byte 对齐。

3.2 monitor_ 字段分析

monitor_ 字段定义在art/runtime/mirror/object.h,类型为uint32_t,主要有下面3个操作函数。

01709f48-bfd4-11ed-bfe3-dac502259ad0.png

操作函数中SetLockWord和CasLockWord函数的入参或GetLockWord函数的返回值都包含LockWord 变量,对monitor_ 字段的操作是通过LockWord 的值进行的。

下面再来看LockWord 定义:

LockWord 类的定义在art/runtime/lock_word.h 文件中,从注释中可以看到LockWord的使用主要有4种状态,如下:

017afa24-bfd4-11ed-bfe3-dac502259ad0.png

LockWord 的设计非常精妙,一个32 位数据的每一位都充分利用,而且很好的区分了不同状态。下面对各状态进行详细说明:

unlocked/thin 状态下31-30 bit 为00,默认状态下为unlocked 状态,当对象进行线程同步时变成thin lock 状态,27-16bit 记录了thin lock重入的次数,15-0 bit 记录了持有该thin lock的线程ID。

fat lock状态下31-30 bit 为01,当对象锁在thin lock状态,且有新的(非owner)线程与其竞争,经过适当的等待期(sched_yield调用、循环获取thin lock 状态)后依然无法拿到锁,则转换为fat lock 状态,并为该对象分配一个Monitor 资源。

hash state状态下31-30 bit 为10,在27-0 bit 存储对象的hash code,当在其它模式下,hash code 会存储在该对象关联的Monitor 对象中。

forwarding address state 状态下31-30 bit 为11,在concurrent copying GC 的copy 阶段,当一个对象被拷贝后,指向拷贝后的对象地址,当线程访问到该对象后,通过该转发地址,访问新的对象。

第29 位为mark bit,通过该bit位可以快速判断是否标记过,避免重复标记。

第28 位为read barrier bit,如果对象LockWorkd的该bit 被设置,则在访问该对象的成员时会进入慢速路径,判断对象是不是需要更新,如果需要更新,则返回拷贝后的对象地址。

四、对象锁代码分析

4.1 首先我们看一段代码

0183f016-bfd4-11ed-bfe3-dac502259ad0.png

这段代码比较简单,主要有下面两个核心点:

1). 在主线程执行的过程中,用obj 对象进行线程同步,并调用obj.wait()函数,使线程阻塞在了obj 对象锁上等待唤醒。

2). main函数中创建匿名线程,该线程首先sleep 2000ms,然后唤醒阻塞在obj 对象锁上线程。

4.2编译TestDemo.java,命令如下:

01934f52-bfd4-11ed-bfe3-dac502259ad0.png

1).Javac 将TestDemo.java 文件编译生成TestDemo*.class文件,java 编译过程中每个类会生成一个class 文件。

2).d8 命令将TestDemo*.class 文件通过编译、重构、重排、压缩、混淆后生成对应的dex (Dalvik Executable file)格式文件。

3).dexdump.exe命令可以查看dex 文件格式的详细信息,如校验信息、dex 头信息、生成dex 的CFG 信息、dex 的反汇编信息等,详细使用方法可以通过dexdump.exe –help 命令查看

通过dexdump.exe –d classes.dex 查看反汇编

其中run 方法指令信息如下:

019aa806-bfd4-11ed-bfe3-dac502259ad0.png

main 函数的指令信息如下:

01a6f476-bfd4-11ed-bfe3-dac502259ad0.png

对部分指令解析如下:

01b3ab30-bfd4-11ed-bfe3-dac502259ad0.png

本文重点分析monitor-enter、monitor-exit、Object.wait()、Object.notify()在虚拟机中的详细实现。

4.3.Object.wait() 流程分析

Object.wait() 的调用关系如下:

01b9e626-bfd4-11ed-bfe3-dac502259ad0.png

Object 类是所有类的父类,任何类中都可以调用public 的wait() 方法,最终调用到虚拟机的monitor.cc 文件的wait 静态方法,

01c49472-bfd4-11ed-bfe3-dac502259ad0.png

首先构造了一个操作obj 的Handle对象h_obj,通过ObjectWaitStart 函数通知jvmti 调试系统发生了JVMTI_EVENT_MONITOR_WAIT 事件。

JVMTI(JVM Tool Interface)是 Java 虚拟机所提供的 native 编程接口,可以用来开发并监控虚拟机,可以查看JVM内部的状态,并控制JVM应用程序的执行。可实现的功能包括但不限于:调试、监控、线程分析、覆盖率分析工具等。

01cf4ca0-bfd4-11ed-bfe3-dac502259ad0.png

首先获得h_obj 对象的LockWord 字段,lock_word.GetState()函数获得当前的锁状态,主要有下面几种情况:

1).hash 或unlocked 状态:

因为调用wait()方法必须持有对象锁,所以不会出现这两种状态,如果出现则抛出IllegalMonitorStateException 异常。

2).thin lock 状态:

当持有该对象锁的线程不是要wait 的线程,也抛出IllegalMonitorStateException 异常,当持有锁的线程与要wait 的线程一致,这时需要将thin lock inflate 为fat lock,inflate 的过程在monitor-enter 指令分析中分析。

当对象锁inflate 为fat lock 状态后,调用Monitor 对象的实例方法Wait让线程进入sleep 状态等待。

4.4 Object.notify() 流程分析

01d96622-bfd4-11ed-bfe3-dac502259ad0.png

这里我们直接分析DoNotify 函数:

01e4f622-bfd4-11ed-bfe3-dac502259ad0.png

通过lock_word.GetState() 获得当前obj 对象的锁状态,主要有下面情况:

1) hash 或unlocked 状态 :

抛出IllegalMonitorStateException 异常。

2).thin lock 状态:

当持有该对象锁的线程不是要notify 的线程,也抛出IllegalMonitorStateException 异常,当持有锁的线程与要notify 的线程一致,这时说明没有需要通知唤醒的线程,直接返回。

3).fat lock 状态:

在Object.notify() 流程中参数notify_all 为false,则直接调用mon->Notify(self);通知唤醒等待线程。

4.5monitor-enter 流程分析

对于解释执行和机器码执行模式,最终都会调用到art/runtime/mirror/object-inl.h 文件Object 对象的MonitorEnter 函数。

01ef32cc-bfd4-11ed-bfe3-dac502259ad0.png

下面来分析Monitor类的静态方法MonitorEnter 函数。

01f5be08-bfd4-11ed-bfe3-dac502259ad0.png

FakeLock 主要用于线程安全性检查,主要在编译期检测

kExtraSpinIters 定义了当对象锁被其它线程持有且为thin lock 时,竞争线程循环获取锁的次数。

02050084-bfd4-11ed-bfe3-dac502259ad0.png

通过lock_word.GetState() 获取锁状态,当锁状态为unlocked 状态时,转换为thin lock 状态,并通过cas 操作更新lock count。

021aee8a-bfd4-11ed-bfe3-dac502259ad0.png

当锁状态为thin lock 状态时,首先获取锁的owner 线程id,如果owner id 与竞争线程id 一致,则有下面两种情况:

如果lock count加1小于等于(1<<12)-1(4095)时,将lock count+1 更新lock count。

如果lock count加1大于(1<<12)-1时(lock count 区域无法存储),则调用InflateThinLocked 函数对thin lock 进行膨胀。

Atrace* 相关的函数主要用于systrace 相关信息的打印,trylock 在这里为false。

02331514-bfd4-11ed-bfe3-dac502259ad0.png

当锁状态为thin lock 状态且锁的owner 线程id 与竞争线程id 不一致,则做一定的等待。

runtime->GetMaxSpinsBeforeThinLockInflation() 的值为50 ,也就是说执行100 次的循环判断锁状态后,再执行50次的sched_yield() 后还未获得锁资源,如果还未拿到锁,则对该锁进行膨胀。sched_yield() 会主动让出当前线程的执行权限,并在某个时间后恢复执行。

023da290-bfd4-11ed-bfe3-dac502259ad0.png

当锁状态已经是fat lock 状态,通过lock_word.FatLockMonitor(); 获取Monitor 对象,并通过Monitor 对象的Lock 函数让线程进入等待状态。

024dff50-bfd4-11ed-bfe3-dac502259ad0.png

当锁状态已经是hash 状态时,直接对锁进行膨胀。

下面看锁膨胀的过程:

0256bbfe-bfd4-11ed-bfe3-dac502259ad0.png

thin lock 的膨胀有两种情形:

1).lock count 的值超过了4095,这时锁的owner 为当前线程,即直接通过Inflate 函数膨胀

2).锁的owner不是当前线程,通过SuspendThreadByThreadId 暂停锁的owner 线程(主要是owner 线程和锁膨胀线程都需要访问对象的LockWord,避免竞态问题),然后通过Inflate 进行膨胀。膨胀完成后再唤醒锁的owner 线程。

再看Inflate 的过程:

026aeeb2-bfd4-11ed-bfe3-dac502259ad0.png

通过MonitorPool::CreateMonitor函数获取一个Monitor 的对象m,并通过m->install(self)函数更新对象的LockWord字段,这时LockWord 字段信息包含fat lock 状态、GC 状态、MonitorId,然后将m 保存在monitor_list_ 中。

monitor_list_中存储了当前虚拟机使用的所有Monitor 对象。在GC 的过程中,通过该链表,访问到Monitor 依赖的对象。如果对象变成垃圾对象,则回收该Monitor,否则更新Monitor 依赖的对象信息。

MonitorId 用于唯一标识一个Monitor,生成的方法可以看monitor_pool.h 中的实现。

再看Monitor::Lock的过程:

该函数的实现较长,省去调试相关的代码。

027fa974-bfd4-11ed-bfe3-dac502259ad0.png

首先介绍Monitor 中最重要的成员monitor_lock_ ,它是Mutex 的实例,通过该实例实现锁相关的核心逻辑。

TryLock 函数主要是通过Mutex的函数实现一定的自旋等待,并设置锁的状态为线程持有的状态。

monitor_lock_.ExclusiveLock(self);在Mutex 的ExclusiveLock函数中通过futex 系统调用实现了线程的阻塞,futex调用代码如下。

028f8dbc-bfd4-11ed-bfe3-dac502259ad0.png

4.6 monitor-exit 流程分析

解释执行和机器码执行模式都会调用到MonitorExit 函数。

029801e0-bfd4-11ed-bfe3-dac502259ad0.png

通过lock_word.GetState()获取LockWord 状态,当状态为hash 或unlocked 状态时,通过FailedUnlock函数抛出异常。

02a795c4-bfd4-11ed-bfe3-dac502259ad0.png

当LockWord 的状态为thin lock 状态时,有下面两种情况:

1).锁的owner 与当前线程不一致,则出错抛出异常。

2).锁的owner 与当前线程为同一线程,当锁有重入时,则将lock count -1,否则设置为unlocked 状态。

02b45c82-bfd4-11ed-bfe3-dac502259ad0.png

当LockWord 的状态为fat lock状态时,获取该对象关联的Monitor 对象,并调用Unlock 函数

02c02dd2-bfd4-11ed-bfe3-dac502259ad0.png

在Unlock 函数中lock_count 为0,说明该线程不在持有该锁,通过SignalWaiterAndReleaseMonitorLock 唤醒阻塞在该锁上的线程。

五、总结

本文简单的阐述了对象锁的使用方式,对象在内存中的结构,并对对象头中关键成员LockWord 进行了分析,最后介绍了synchronized、Object.wait()和Object.notify()在虚拟机中的实现流程。






审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • Android
    +关注

    关注

    12

    文章

    3935

    浏览量

    127352
  • JAVA
    +关注

    关注

    19

    文章

    2966

    浏览量

    104707
  • JVM
    JVM
    +关注

    关注

    0

    文章

    158

    浏览量

    12220
  • 虚拟机
    +关注

    关注

    1

    文章

    914

    浏览量

    28161

原文标题:虚拟机中对象锁实现分析

文章出处:【微信号:LinuxDev,微信公众号:Linux阅码场】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    介绍VirtualBox虚拟机的构建方法

    本系列文章将向大家介绍嵌入式系统开发的各方面知识。本文将向大家介绍VirtualBox虚拟机的构建方法。一、什么是虚拟机二、主流
    发表于 11-08 06:21

    VM虚拟机详细使用安装教程

    VM虚拟机详细使用安装教程
    发表于 07-30 16:16 0次下载

    基于虚拟机技术的DSC仿真系统设计

    提出了基于虚拟机技术的DCS仿真系统的实现方式,描述了虚拟控制器的具体实现方法虚拟机技术的其他
    发表于 12-03 17:26 26次下载
    基于<b class='flag-5'>虚拟机</b>技术的DSC仿真系统设计

    基于虚拟机技术的DCS仿真系统设计与实现

    提出了基于虚拟机技术的DCS仿真系统的实现方式,描述了虚拟控制器的具体实现方法虚拟机技术的其他
    发表于 01-16 15:04 2178次阅读
    基于<b class='flag-5'>虚拟机</b>技术的DCS仿真系统设计与<b class='flag-5'>实现</b>

    虚拟机镜像去冗余方法

    挑战性的研究热点.由于虚拟机镜像之间存在大量重复性的数据块,高效的去冗余方法对于虚拟机镜像管理至关重要.然而,传统的去冗余方法由于需要巨大的资源开销,会对平台中托管的
    发表于 01-17 09:50 0次下载

    基于硬件虚拟化的虚拟机进程代码分页式度量方法

    云环境下恶意软件可利用多种手段篡改虚拟机( VM)中关键业务代码,威胁其运行的稳定性。传统的基于主机的度量系统易被绕过或攻击而失效,针对在虚拟机监视器( VMM)层难以获取虚拟机中运行
    发表于 03-29 17:40 0次下载
    基于硬件<b class='flag-5'>虚拟</b>化的<b class='flag-5'>虚拟机</b>进程代码分页式度量<b class='flag-5'>方法</b>

    如何在Win下安装linux的虚拟机详细安装方法资料概述

    本文档的主要内容详细介绍的是如何在Win7下安装linux的虚拟机详细安装方法资料概述免费下载。
    发表于 11-28 15:03 3次下载

    什么是区块链虚拟机和普通虚拟机有啥区别

    区块链技术领域基础设施——虚拟机,是实现智能合约系统最为关键和核心的技术。智能合约不仅是业务逻辑的载体,同时又扎扎实实地落在了技术实现的层面。由此可见,
    发表于 03-04 10:50 4957次阅读

    linux虚拟机的联网方法

    虚拟机安装linux系统无法上网的解决方法
    发表于 05-31 09:27 1527次阅读
    linux<b class='flag-5'>虚拟机</b>的联网<b class='flag-5'>方法</b>

    虚拟机的设计与实现:C\C++

    虚拟机的设计与实现:C\C++
    发表于 02-21 15:10 0次下载

    虚拟机洞察:实现应用感知型基础架构的关键路径

    电子发烧友网站提供《虚拟机洞察:实现应用感知型基础架构的关键路径.pdf》资料免费下载
    发表于 08-29 11:07 0次下载
    <b class='flag-5'>虚拟机</b>洞察:<b class='flag-5'>实现</b>应用感知型基础架构的<b class='flag-5'>关键</b>路径

    linux虚拟机怎么运行代码

    运行代码是Linux虚拟机中的常见操作,本文将详细介绍如何运行代码。 首先,要运行代码,你需要先安装好Linux虚拟机,并确保能够顺利运行。接下来,你需要打开
    的头像 发表于 11-17 10:12 5127次阅读

    Docker与虚拟机的区别

    Docker和虚拟机是两种不同的虚拟化技术,它们在实现方式、资源消耗、运行性能等方面存在许多差异。本文将会详细介绍它们的区别。 一、
    的头像 发表于 11-23 09:37 9764次阅读

    怎么安装linux虚拟机

    在计算机领域,虚拟机是一种软件程序,它允许在主操作系统上运行多个虚拟操作系统。Linux虚拟机在开发、测试和学习等环境中得到广泛应用。本文将详细介绍
    的头像 发表于 11-23 10:50 1093次阅读

    虚拟机ubuntu怎么联网

    与外部网络通信。本文将详细介绍虚拟机Ubuntu的网络连接方法以及一些常见的网络问题解决办法。 一、虚拟机网络概述
    的头像 发表于 12-27 16:51 977次阅读