详解Java对象结构与对象锁的升级

目录
  • 1.Java对象结构
  • 2.MarkWord的结构信息
  • 3.无锁、偏向锁、轻量级锁和重量级锁
  • 总结

1. Java对象结构

Java对象结构包括三部分:对象头、对象体和填充字节,如图所示:

对象头又包括三个字段:

  • 第一个字段叫作Mark Word(标记字),用于存储自身运行时的数据,例如GC标志位、哈希码、锁状态等信息。
  • 第二个字段叫作Class Pointer(类对象指针),用于存放方法区Class对象的地址,虚拟机通过这个指针来确定这个对象是哪个类的实例。
  • 第三个字段叫作Array Length(数组长度)。如果对象是一个Java数组,那么此字段必须有,用于记录数组长度的数据;如果对象不是一个Java数组,那么此字段不存在,所以这是一个可选字段。

在32位JVM虚拟机中,Mark Word和Class Pointer这两部分都是32位的;在64位JVM虚拟机中,Mark Word和Class Pointer这两部分都是64位的。而我们需要重点理解的时mark word , 因为它跟synchronized的底层原理有关。

2. Mark Word的结构信息

Java内置锁的状态总共有4种,级别由低到高依次为:无锁、偏向锁、轻量级锁和重量级锁。其实在JDK 1.6之前,Java内置锁还是一个重量级锁,是一个效率比较低下的锁,在JDK 1.6之后,JVM为了提高锁的获取与释放效率,对synchronized的实现进行了优化,引入了偏向锁和轻量级锁,从此以后Java内置锁的状态就有了4种,并且4种状态会随着竞争的情况逐渐升级,而且是不可逆的过程,即不可降级,也就是说只能进行锁升级。

1、不同锁状态下的Mark Word字段结构:

Mark Word字段的结构与Java内置锁的状态强相关。为了让Mark Word字段存储更多的信息,JVM将Mark Word最低两个位设置为Java内置锁状态位,不同锁状态下的32位Mark Word结构如表所示:

64位的Mark Word与32位的Mark Word结构相似,结构如表所示:

2、64位Mark Word的介绍:

由于目前主流的JVM都是64位,因此我们使用64位的Mark Word。接下来对64位的Mark Word中各部分的内容进行具体介绍。

(1) lock:锁状态标记位,占两个二进制位,由于希望用尽可能少的二进制位表示尽可能多的信息,因此设置了lock标记。该标记的值不同,整个Mark Word表示的含义就不同。

(2) biased_lock:对象是否启用偏向锁标记,只占1个二进制位。为1时表示对象启用偏向锁,为0时表示对象没有偏向锁。lock和biased_lock两个标记位组合在一起共同表示Object实例处于什么样的锁状态。二者组合的含义具体如表2-3所示。

lock和biased_lock两个标记位组合在一起共同表示Object实例处于什么样的锁状态。二者组合的含义具体如表所示:

(3) ptr_to_lock_record:占62位,在轻量级锁的状态下指向栈帧中锁记录的指针。

(4) ptr_to_heavyweight_monitor:占62位,在重量级锁的状态下指向对象监视器的指针。

3. 无锁、偏向锁、轻量级锁和重量级锁

在JDK 1.6版本之前,所有的Java内置锁都是重量级锁。重量级锁会造成CPU在用户态和核心态之间频繁切换,所以代价高、效率低。JDK 1.6版本为了减少获得锁和释放锁所带来的性能消耗,引入了偏向锁和轻量级锁的实现。所以,在JDK 1.6版本中内置锁一共有4种状态:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态,这些状态随着竞争情况逐渐升级。内置锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能再降级成偏向锁。这种能升级却不能降级的策略,其目的是提高获得锁和释放锁的效率。

(1) 无锁状态 :

Java对象刚创建时还没有任何线程来竞争,说明该对象处于无锁状态(无线程竞争它),这时偏向锁标识位是0,锁状态是01;

(2) 偏向锁状态:

偏向锁是指一段同步代码一直被同一个线程所访问,那么该线程会自动获取锁,降低获取锁的代价。如果内置锁处于偏向状态,当有一个线程来竞争锁时,先用偏向锁,表示内置锁偏爱这个线程,这个线程要执行该锁关联的同步代码时,不需要再做任何检查和切换。偏向锁在竞争不激烈的情况下效率非常高。

偏向锁状态的Mark Word会记录内置锁自己偏爱的线程ID,内置锁会将该线程当作自己的熟人,这时偏向锁标识位是1,锁状态是01;

(3) 轻量级锁状态:

当有两个线程开始竞争这个锁对象时,情况就发生变化了,不再是偏向(独占)锁了,锁会升级为轻量级锁,两个线程公平竞争,哪个线程先占有锁对象,锁对象的Mark Word就指向哪个线程的栈帧中的锁记录。这时偏向锁标识位是0,锁状态是00;

当锁处于偏向锁,又被另一个线程企图抢占时,偏向锁就会升级为轻量级锁。企图抢占的线程会通过自旋的形式尝试获取锁,不会阻塞抢锁线程,以便提高性能。

自旋原理非常简单,如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要进行内核态和用户态之间的切换来进入阻塞挂起状态,它们只需要等一等(自旋),等持有锁的线程释放锁后即可立即获取锁,这样就避免了用户线程和内核切换的消耗。

但是,线程自旋是需要消耗CPU的,如果一直获取不到锁,那么线程也不能一直占用CPU自旋做无用功,所以需要设定一个自旋等待的最大时间。JVM对于自旋周期的选择,JDK 1.6之后引入了适应性自旋锁,适应性自旋锁意味着自旋的时间不是固定的,而是由前一次在同一个锁上的自旋时间以及锁的拥有者的状态来决定的。线程如果自旋成功了,下次自旋的次数就会更多,如果自旋失败了,自旋的次数就会减少。

如果持有锁的线程执行的时间超过自旋等待的最大时间仍没有释放锁,就会导致其他争用锁的线程在最大等待时间内还是获取不到锁,自旋不会一直持续下去,这时争用线程会停止自旋进入阻塞状态,该锁膨胀为重量级锁。

(4) 重量级锁状态:

重量级锁会让其他申请的线程之间进入阻塞,性能降低。重量级锁也叫同步锁,这个锁对象MarkWord再次发生变化,会指向一个监视器对象,该监视器对象用集合的形式来登记和管理排队的线程。这时偏向锁标识位是0,锁状态是10;

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注我们的更多内容!

(0)

相关推荐

  • 浅谈java对象结构 对象头 Markword

    概述 对象实例由对象头.实例数据组成,其中对象头包括markword和类型指针,如果是数组,还包括数组长度; | 类型 | 32位JVM | 64位JVM| | ------ ---- | ------------| --------- | | markword | 32bit | 64bit | | 类型指针 | 32bit |64bit ,开启指针压缩时为32bit | | 数组长度 | 32bit |32bit | header.png compressed_header.png 可以看到

  • Java 中组合模型之对象结构模式的详解

    Java 中组合模型之对象结构模式的详解 一.意图 将对象组合成树形结构以表示"部分-整体"的层次结构.Composite使得用户对单个对象和组合对象的使用具有一致性. 二.适用性 你想表示对象的部分-整体层次结构 你希望用户忽略组合对象与单个对象的不同,用户将统一使用组合结构中的所有对象. 三.结构 四.代码 public abstract class Component { protected String name; //节点名 public Component(String n

  • Java类锁、对象锁、私有锁冲突测试

    类锁和对象锁是否会冲突?对象锁和私有锁是否会冲突?通过实例来进行说明. 一.相关约定 为了明确后文的描述,先对本文涉及到的锁的相关定义作如下约定: 1. 类锁:在代码中的方法上加了static和synchronized的锁,或者synchronized(xxx.class)的代码段,如下文中的increament(): 2.对象锁:在代码中的方法上加了synchronized的锁,或者synchronized(this)的代码段,如下文中的synOnMethod()和synInMethod():

  • Java 中桥接模式——对象结构型模式的实例详解

    Java  中桥接模式--对象结构型模式的实例详解 一.意图 将抽象部分与它的实现部分分离,使他们都可以独立的变化. 二.适用性 以下一些情况使用Bridge模式 你不希望在抽象和它的实现部分之间有一个固定的绑定关系.例如这种情况可能因为,在程序运行时刻实现部分应可以被选择或者切换. 类的抽象以及它的实现都应该可以通过生成子类的方法加以扩充.这时Bridge模式使你可以对不同的抽象接口和实现部分进行组合,并分别对他们进行扩充. 对一个抽象的实现部分的修改应对客户不产生影响,即客户代码不必重新编译

  • 透彻理解Java中Synchronized(对象锁)和Static Synchronized(类锁)的区别

    本文讲述了Java中Synchronized(对象锁)和Static Synchronized(类锁)的区别.分享给大家供大家参考,具体如下: Synchronized和Static Synchronized区别 通过分析这两个用法的分析,我们可以理解java中锁的概念.一个是实例锁(锁在某一个实例对象上,如果该类是单例,那么该锁也具有全局锁的概念),一个是全局锁(该锁针对的是类,无论实例多少个对象,那么线程都共享该锁).实例锁对应的就是synchronized关键字,而类锁(全局锁)对应的就是

  • 详解java 对象锁与类锁

    一.什么是对象锁 对象锁也叫方法锁,是针对一个对象实例的,它只在该对象的某个内存位置声明一个标识该对象是否拥有锁,所有它只会锁住当前的对象,而并不会对其他对象实例的锁产生任何影响,不同对象访问同一个被synchronized修饰的方法的时候不会阻塞, 例如: public class MyObject { private synchronized void method1(){ try { System.out.println(Thread.currentThread().getName());

  • 详解java并发之重入锁-ReentrantLock

    前言 目前主流的锁有两种,一种是synchronized,另一种就是ReentrantLock,JDK优化到现在目前为止synchronized的性能已经和重入锁不分伯仲了,但是重入锁的功能和灵活性要比这个关键字多的多,所以重入锁是可以完全替代synchronized关键字的.下面就来介绍这个重入锁. 正文 ReentrantLock重入锁是Lock接口里最重要的实现,也是在实际开发中应用最多的一个,我这篇文章更接近实际开发的应用场景,为开发者提供直接上手应用.所以不是所有方法我都讲解,有些冷门

  • 详解Java并发编程之内置锁(synchronized)

    简介 synchronized在JDK5.0的早期版本中是重量级锁,效率很低,但从JDK6.0开始,JDK在关键字synchronized上做了大量的优化,如偏向锁.轻量级锁等,使它的效率有了很大的提升. synchronized的作用是实现线程间的同步,当多个线程都需要访问共享代码区域时,对共享代码区域进行加锁,使得每一次只能有一个线程访问共享代码区域,从而保证线程间的安全性. 因为没有显式的加锁和解锁过程,所以称之为隐式锁,也叫作内置锁.监视器锁. 如下实例,在没有使用synchronize

  • 详解Java中用于查找对象哈希码值的hashCode()函数

    理解 hashCode() 的作用是获取哈希码,也称为散列码:它实际上是返回一个int整数.这个哈希码的作用是确定该对象在哈希表中的索引位置. hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode() 函数. 虽然,每个Java类都包含hashCode() 函数.但是,仅仅当创建并某个"类的散列表"(关于"散列表"见下面说明)时,该类的hashCode() 才有用(作用是:确定该类的每一个对象在散列表中的

  • 详解Java多线程编程中互斥锁ReentrantLock类的用法

    0.关于互斥锁 所谓互斥锁, 指的是一次最多只能有一个线程持有的锁. 在jdk1.5之前, 我们通常使用synchronized机制控制多个线程对共享资源的访问. 而现在, Lock提供了比synchronized机制更广泛的锁定操作, Lock和synchronized机制的主要区别: synchronized机制提供了对与每个对象相关的隐式监视器锁的访问, 并强制所有锁获取和释放均要出现在一个块结构中, 当获取了多个锁时, 它们必须以相反的顺序释放. synchronized机制对锁的释放是

  • 详解Java中的不可变对象

    不可变对象想必大部分朋友都不陌生,大家在平时写代码的过程中100%会使用到不可变对象,比如最常见的String对象.包装器对象等,那么到底为何Java语言要这么设计,真正意图和考虑点是什么?可能一些朋友没有细想过这些问题,今天我们就来聊聊跟不可变对象有关的话题. 一.什么是不可变对象 下面是<Effective Java>这本书对于不可变对象的定义: 不可变对象(Immutable Object):对象一旦被创建后,对象所有的状态及属性在其生命周期内不会发生任何变化. 从不可变对象的定义来看,

  • 详解Java对象的内存布局

    前言 今天来讲些抽象的东西 -- 对象头,因为我在学习的过程中发现很多地方都关联到了对象头的知识点,例如JDK中的 synchronized锁优化 和 JVM 中对象年龄升级等等.要深入理解这些知识的原理,了解对象头的概念很有必要,而且可以为后面分享 synchronized 原理和 JVM 知识的时候做准备. 对象内存构成 Java 中通过 new 关键字创建一个类的实例对象,对象存于内存的堆中并给其分配一个内存地址,那么是否想过如下这些问题: 这个实例对象是以怎样的形态存在内存中的? 一个O

  • 详解Java对象的强、软、弱和虚引用+ReferenceQueue

    详解Java对象的强.软.弱和虚引用+ReferenceQueue 一.强引用(StrongReference) 强引用是使用最普遍的引用.如果一个对象具有强引用,那垃圾回收器绝不会回收它.当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题. 二.软引用(SoftReference) 如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它:如果内存空间不足了,就会回收这些对象的内存.只要垃圾回

  • 详解Java对象序列化为什么要使用SerialversionUID

    1.首先谈谈为什么要序列化对象 - 把对象转换为字节序列的过程称为对象的序列化. - 把字节序列恢复为对象的过程称为对象的反序列化. 对象的序列化主要有两种用途: 1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中: 2) 在网络上传送对象的字节序列. 在很多应用中,需要对某些对象进行序列化,让它们离开内存空间,入住物理硬盘,以便长期保存.比如最常见的是Web服务器中的Session对象,当有 10万用户并发访问,就有可能出现10万个Session对象,内存可能吃不消,于是Web容器

  • 详解Java对象创建的过程及内存布局

    一.对象的内存布局 对象头 对象头主要保存对象自身的运行时数据和用于指定该对象属于哪个类的类型指针. 实例数据 保存对象的有效数据,例如对象的字段信息,其中包括从父类继承下来的. 对齐填充 对齐填充不是必须存在的,没有特别的含义,只起到一个占位符的作用. 二.对象的创建过程 实例化一个类的对象的过程是一个典型的递归过程. 在准备实例化一个类的对象前,首先准备实例化该类的父类,如果该类的父类还有父类,那么准备实例化该类的父类的父类,依次递归直到递归到Object类. 此时,首先实例化Object类

随机推荐