草庐IT

Java synchronized锁升级过程验证

heimuye 2023-04-16 原文

Java对象结构

 


 

 

一个对象包括三部分:

对象头

实例数据

对其填充

 

对象头:

Mark Word:用于存储对象自身运行时的数据,如哈希码(Hash Code),GC分代年龄,锁状态标志,偏向线程ID、偏向时间戳等信息,它会根据对象的状态复用自己的存储空间。它是实现轻量级锁和偏向锁的关键。

Klass Pointer:存储指向方法区对象类型指针

Array Length:如果是数组,还包括数组长度

 

如果对象为非数组类型,用2字宽存储对象头。

如果对象为数组类型,用3字宽存储对象头。

 

在32位虚拟机中,1字宽等于4字节,即32bit。在64位虚拟机中,1字宽等于8字节,即64bit。如下表所示:

 


 

实例数据

存放类的属性数据信息,包括父类的属性信息,如果是数组的实例部分还包括数组的长度,这部分内存按4字节对齐;

 

对齐填充

不是必须部分,由于虚拟机要求对象起始地址必须是8字节的整数倍,对齐填充仅仅是为了使字节对齐。

 

对象头结构

Mark Word:

Java对象头里的Mark Word里默认存储对象的HashCode、分代年龄和锁标记位。

32位JVM 的Mark Word的默认存储结构如下:

 


在运行期间,Mark Word里存储的数据会随着锁标志位的变化而变化。Mark Word可能变 化为存储以下4种数据

完整结构: 


 

在64位虚拟机下,Mark Word是64bit大小的,其存储结构如下:

 


 这里我们主要关注这3个部分:锁状态、是否偏向锁、锁标志位。

 

 

 

 

 锁标记位(lock):该标记值表示对象锁的状态。

 

是否为偏向锁(biased_lock):对象是否启用偏向锁标记,只占1个二进制位。为1时表示对象启用偏向锁,为0时表示对象没有偏向锁。

 

class pointer:

这一部分用于存储对象的类型指针,该指针指向它的类元数据,JVM通过这个指针确定对象是哪个类的实例。该指针的位长度为JVM的一个字大小,即32位的JVM为32位,64位的JVM为64位。而64位的对象头有点浪费空间,JVM默认会开启指针压缩,所以基本上也是按32位的形式记录对象头的。

开启压缩指针(-XX:+UseCompressedOops) 关闭压缩指针(-XX:-UseCompressedOops)

,其中,oop即ordinary object pointer普通对象指针。

 

array length:

如果对象是一个数组,那么对象头还需要有额外的空间用于存储数组的长度,这部分数据的长度也随着JVM架构的不同而不同:32位的JVM上,长度为32位;64位JVM则为64位。64位JVM如果开启+UseCompressedOops选项,该区域长度也将由64位压缩至32位。

 

synchronized锁

介绍完对象头,现在我们来介绍synchronized关键字。synchronized是对象锁,锁状态变化就体现在上面介绍的Mark Word中的偏向锁以及锁标志位。

锁升级介绍:

我们先介绍下锁升级过程。

JD6之后分为无锁,偏向锁,轻量级锁,重量级锁。其中偏向锁->轻量级锁->重量级锁的升级过程不可逆。

 

 

 

 

 

偏向锁:当一个线程第一次获取到锁之后,再次申请就可以直接取到锁

 

核心思想:

一开始无锁状态,JVM会默认开启“匿名”偏向的一个状态,就是一开始线程还未持有锁的时候,就预先设置一个匿名偏向锁,等一个线程持有锁之后,就会利用CAS操作将线程ID设置到对象的mark word 的高54位上【64位虚拟机】。如果一个线程获得了锁,那么锁就进入偏向模式,此时Mark Word的结构也就变为偏向锁结构,当该线程再次请求锁时,无需再做任何同步操作,即获取锁的过程只需要检查Mark Word的锁标记位为偏向锁以及当前线程ID等于Mark Word的ThreadID即可,这样就省去了大量有关锁申请的操作。

 

轻量级锁:没有多线程竞争,但有多个线程交替执行。

轻量级锁是由偏向锁升级而来,当存在第二个线程申请同一个锁对象时,偏向锁将会升级为轻量级锁,Mark Word 的结构也变为轻量级锁的结构。注意这里的第二个线程只是申请锁,不存在两个线程同时竞争锁,可以是一前一后地交替执行同步块。

执行同步代码块之前,JVM会在线程的栈帧中创建一个锁记录(Lock Record),并将Mark Word拷贝复制到锁记录中。然后尝试通过CAS操作将Mark Word中的锁记录的指针,指向创建的Lock Record。如果成功表示获取锁状态成功。如果失败,则进入自旋获取锁状态。如果自旋获取锁也失败了,则升级为重量级锁,也就是把线程阻塞起来,等待唤醒。

 

自旋锁与自适应自旋锁

轻量级锁失败后,虚拟机为了避免线程真实地在操作系统层面挂起,还会进行一项称为自旋锁的优化手段。

自旋锁:许多情况下,当线程没有获得monitor对象的所有权时,就会进入阻塞,当持有锁的线程释放了锁,当前线程才可以再去竞争锁,但是如果按照这样的规则,就会浪费大量的性能在阻塞和唤醒的切换上,特别是线程占用锁的时间很短的话。

为了避免阻塞和唤醒的切换,在没有获得锁的时候就不进入阻塞,而是不断地循环检测锁是否被释放,这就是自旋。在占用锁的时间短的情况下,自旋锁表现的性能是很高的。

但是它也存在缺点:如果锁被其他线程长时间占用,一直不释放CPU,那么自旋的次数就会变多,占用cpu时间变长导致性能变差。当然我们也可以设置自旋锁的自旋次数,当自旋一定的次数(时间)后就挂起。但是如果设置次数少了或者多了都会导致性能受到影响,所以在JDK1.6引入了自适应性自旋锁。

自适应自旋锁:这种相当于是对上面自旋锁优化方式的进一步优化,它的自旋的次数不再固定,其自旋的次数由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。

表现是如果此次自旋成功了,很有可能下一次也能成功,于是允许自旋的次数就会更多,反之,如果很少有线程能够自旋成功,很有可能下一次也是失败,则自旋次数就更少。这样能最大化利用资源,随着程序运行和性能监控信息的不断完善,虚拟机对锁的状况预测会越来越准确,也就变得越来越智能。

 

重量级锁:有多线程竞争,线程获取不到锁进入阻塞状态。

重量级锁是由轻量级锁升级而来,当同一时间有多个线程竞争锁时,锁就会被升级成重量级锁,此时其申请锁带来的开销也就变大。

 

锁升级验证

锁升级过程是非常复杂的,很多理论知识很难用实践验证,这里我们只验证锁状态的变化过程,也就是Mark Word中锁标志位的变化。

这里我们引用一个 Maven 依赖 jol(Java Object Layout),这个类提供了工具方法可以打印虚拟机状态。

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

接下来查看虚拟机信息。

System.out.println(VM.current().details());

可以看到是64位的jvm,并且开启了对象指针压缩和类型指针压缩。

 

开启偏向锁

我们设计两个线程,线程0和线程1,分别获取对象锁,然后打印对象头看锁状态变化。

先看开启偏向锁的情况:

-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0

 


 

无锁竞争,偏向锁->轻量级锁


 

 

执行结果:


 

 

这里先介绍下如何看对象头状态。前面说过64位jvm对象头的Mark Word占8个字节,所以这里05 00 00 00 00 00 00 00都是Mark Word。由于jvm采用大端模式存储字节,将高位字节存放在低地址,将低位字节存放在高地址,所以这里对照对象头表格来看要倒序,即00000101对应这8位。可以看到这时对象处于偏向锁状态。

 


 


 



 分析执行结果:

 

线程0和线程1无并发冲突,线程0两次都是获取的偏向锁,验证了前面关于偏向锁的定义。线程1获取锁的时候发现当前占有锁的是线程0,于是升级为轻量级锁。

 

有锁竞争,偏向锁->重量级锁


 

 

执行结果:

 


 


 



 

 

分析执行结果:

线程0和线程1存在锁竞争,于是从偏向锁升级为重量级锁。

 

关闭偏向锁

-XX:-UseBiasedLocking:


 

 

无锁竞争,无锁->轻量级锁:

 


 


 



 

 

执行结果分析:

关闭偏向锁,默认是无锁状态。无锁竞争,从无锁状态升级为轻量级锁。

 

有锁竞争,无锁->重量级锁:

 


 


 


 

 

 

执行结果分析:

关闭偏向锁,默认是无锁状态。有锁竞争,从无锁状态升级为重量级锁。

 

 


 

有关Java synchronized锁升级过程验证的更多相关文章

  1. ruby-on-rails - 如何验证 update_all 是否实际在 Rails 中更新 - 2

    给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru

  2. ruby - 具有身份验证的私有(private) Ruby Gem 服务器 - 2

    我想安装一个带有一些身份验证的私有(private)Rubygem服务器。我希望能够使用公共(public)Ubuntu服务器托管内部gem。我读到了http://docs.rubygems.org/read/chapter/18.但是那个没有身份验证-如我所见。然后我读到了https://github.com/cwninja/geminabox.但是当我使用基本身份验证(他们在他们的Wiki中有)时,它会提示从我的服务器获取源。所以。如何制作带有身份验证的私有(private)Rubygem服务器?这是不可能的吗?谢谢。编辑:Geminabox问题。我尝试“捆绑”以安装新的gem..

  3. ruby - 通过 rvm 升级 ruby​​gems 的问题 - 2

    尝试通过RVM将RubyGems升级到版本1.8.10并出现此错误:$rvmrubygemslatestRemovingoldRubygemsfiles...Installingrubygems-1.8.10forruby-1.9.2-p180...ERROR:Errorrunning'GEM_PATH="/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/ruby-1.9.2-p180@global:/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/rub

  4. ruby-on-rails - 如果为空或不验证数值,则使属性默认为 0 - 2

    我希望我的UserPrice模型的属性在它们为空或不验证数值时默认为0。这些属性是tax_rate、shipping_cost和price。classCreateUserPrices8,:scale=>2t.decimal:tax_rate,:precision=>8,:scale=>2t.decimal:shipping_cost,:precision=>8,:scale=>2endendend起初,我将所有3列的:default=>0放在表格中,但我不想要这样,因为它已经填充了字段,我想使用占位符。这是我的UserPrice模型:classUserPrice回答before_val

  5. ruby-on-rails - 如何验证非模型(甚至非对象)字段 - 2

    我有一个表单,其中有很多字段取自数组(而不是模型或对象)。我如何验证这些字段的存在?solve_problem_pathdo|f|%>... 最佳答案 创建一个简单的类来包装请求参数并使用ActiveModel::Validations。#definedsomewhere,atthesimplest:require'ostruct'classSolvetrue#youcouldevencheckthesolutionwithavalidatorvalidatedoerrors.add(:base,"WRONG!!!")unlesss

  6. ruby-on-rails - 项目升级后 Pow 不会更改 ruby​​ 版本 - 2

    我在我的Rails项目中使用Pow和powifygem。现在我尝试升级我的ruby​​版本(从1.9.3到2.0.0,我使用RVM)当我切换ruby​​版本、安装所有gem依赖项时,我通过运行railss并访问localhost:3000确保该应用程序正常运行以前,我通过使用pow访问http://my_app.dev来浏览我的应用程序。升级后,由于错误Bundler::RubyVersionMismatch:YourRubyversionis1.9.3,butyourGemfilespecified2.0.0,此url不起作用我尝试过的:重新创建pow应用程序重启pow服务器更新战俘

  7. ruby - 如何在 Lion 上安装 Xcode 4.6,需要用 RVM 升级 ruby - 2

    我实际上是在尝试使用RVM在我的OSX10.7.5上更新ruby,并在输入以下命令后:rvminstallruby我得到了以下回复:Searchingforbinaryrubies,thismighttakesometime.Checkingrequirementsforosx.Installingrequirementsforosx.Updatingsystem.......Errorrunning'requirements_osx_brew_update_systemruby-2.0.0-p247',pleaseread/Users/username/.rvm/log/138121

  8. ruby-on-rails - 如何将验证与模型分开 - 2

    我有一些非常大的模型,我必须将它们迁移到最新版本的Rails。这些模型有相当多的验证(User有大约50个验证)。是否可以将所有这些验证移动到另一个文件中?说app/models/validations/user_validations.rb。如果可以,有人可以提供示例吗? 最佳答案 您可以为此使用关注点:#app/models/validations/user_validations.rbrequire'active_support/concern'moduleUserValidationsextendActiveSupport:

  9. ruby-on-rails - 跳过状态机方法的所有验证 - 2

    当我的预订模型通过rake任务在状态机上转换时,我试图找出如何跳过对ActiveRecord对象的特定实例的验证。我想在reservation.close时跳过所有验证!叫做。希望调用reservation.close!(:validate=>false)之类的东西。仅供引用,我们正在使用https://github.com/pluginaweek/state_machine用于状态机。这是我的预订模型的示例。classReservation["requested","negotiating","approved"])}state_machine:initial=>'requested

  10. ruby - 如何在 Rails 4 中使用表单对象之前的验证回调? - 2

    我有一个服务模型/表及其注册表。在表单中,我几乎拥有服务的所有字段,但我想在验证服务对象之前自动设置其中一些值。示例:--服务Controller#创建Action:defcreate@service=Service.new@service_form=ServiceFormObject.new(@service)@service_form.validate(params[:service_form_object])and@service_form.saverespond_with(@service_form,location:admin_services_path)end在验证@ser

随机推荐