一篇真正搞懂底层JAVA线程锁机制!!!

锁升级过程:

JAVA线程锁机制是怎样的? 偏向锁、轻量级锁、重量级锁有什么区别?锁机制是如何升级的?

简单来说:

1、JAVA的锁就是在对象的Markword中记录一个锁状态。无锁,偏向锁,轻量级锁,重量级锁对应不同的锁状态。

2、JAVA的锁机制就是根据资源竞争的激烈程度不断进行锁升级的过程

3、整个升级过程,主要就是new对象后,若开启了偏向锁,获得偏向锁,轻度竞争就升级为轻量级锁,重度竞争(耗时过长,wait()等)就从轻量级锁变到重量级锁,或直接从偏向锁变到重量级锁。

而JVM默认不打开偏向锁,对默认开启偏向锁的时间为4秒,比如让主线程休眠4s以上,后面创建的对象就会转换成偏向锁。

不开启偏向锁,就变成普通对象,根据后续有无偏向锁,锁竞争情况转化成偏向锁状态或轻量级重量级锁状态。

  • JVM相关参数

    • -XX:UseBiasedLocking : 是否打开偏向锁。默认不打开的。

    • -XX:BiasedLockingStartupDelay 默认是4秒

  • 竞争程度,有以下评估方式:

    • 基于CAS操作失败次数

    • 线程阻塞情况(阻塞过长时间)

    • 系统的负载和性能指标(CPU使用率较高)

    • 自适应自旋优化机制(JVM 会根据之前自旋获取锁的历史数据来动态调整自旋的次数和策略)

在 Java 的线程锁机制中,对象头中的 Markword 扮演着重要角色,它存储了与对象的锁状态等相关的关键信息。以下是关于 Markword 底层相关内容的详细介绍:

对象头与 Markword 概述

在 Java 对象在内存中的布局中,对象头(Object Header)是对象在内存中的一部分,它包含了两部分信息:一部分是用于存储对象的哈希码、分代年龄等元信息的区域,另一部分就是 Markword。Markword 主要用于存储对象的锁状态以及一些相关的标记信息,它的长度在不同的 Java 虚拟机实现以及不同的操作系统下可能会有所不同,但通常是 32 位(在 32 位虚拟机中)或 64 位(在 64 位虚拟机中)。

Markword 存储的信息及格式

以 64 位虚拟机为例,Markword 存储的信息及格式大致如下:

状态偏向锁标志位(1 位)锁标志位(2 位)分代年龄(4 位)对象指针(64 位虚拟机中剩余的位)
无锁状态001存储对象的分代年龄信息,用于垃圾回收时的分代判断指向对象在内存中的实际位置(在未开启偏向锁时)
偏向锁状态101存储对象的分代年龄信息,用于垃圾回收时的分代判断指向持有偏向锁的线程 ID(开启偏向锁时)
轻量级锁状态000存储对象的分代年龄信息,用于垃圾回收时的分代判断指向栈帧中的锁记录(Lightweight Lock Record)
重量级锁状态010存储对象的分代年龄信息,用于垃圾回收时的分代判断指向互斥量(Mutex),也就是获取该对象锁的线程所对应的互斥量

下面分别详细介绍不同锁状态下 Markword 的具体情况:

无锁状态

当对象处于无锁状态时:

  • 锁标志位:为 “01”,这是无锁状态的标识。

  • 偏向锁标志位:为 “0”,表示未启用偏向锁。

  • 分代年龄:正常存储对象的分代年龄信息,这个信息在垃圾回收的分代算法中会用到,用于判断对象属于哪一代(比如新生代、老年代等),以便采取不同的垃圾回收策略。

  • 对象指针:指向对象在内存中的实际位置,方便在需要访问对象时能够准确找到它。

偏向锁状态

偏向锁是 Java 为了提高单线程访问带锁对象的性能而引入的一种优化机制。当对象处于偏向锁状态时:

  • 锁标志位:同样是 “01”,与无锁状态的锁标志位相同,这是因为偏向锁本质上也是一种特殊的无锁状态,只是在特定情况下会偏向于某一个线程访问。

  • 偏向锁标志位:变为 “1”,表示该对象已经启用了偏向锁机制。

  • 分代年龄:依然存储对象的分代年龄信息,用于垃圾回收相关操作。

  • 对象指针:此时不再指向对象在内存中的实际位置,而是指向持有偏向锁的线程 ID。这意味着当这个线程再次访问该对象时,只要检查到对象处于偏向锁状态且持有偏向锁的线程就是自己,就可以直接进入对象的访问,无需再进行常规的锁获取操作,从而大大提高了单线程访问带锁对象的效率。

轻量级锁状态

轻量级锁是在多线程竞争不太激烈的情况下使用的一种相对较轻量级的锁机制。当对象处于轻量级锁状态时:

  • 锁标志位:变为 “00”,这是轻量级锁的标识。

  • 偏向锁标志位:变回 “0”,因为轻量级锁与偏向锁是不同的锁机制,启用轻量级锁时偏向锁机制会暂时失效。

  • 分代年龄:继续存储对象的分代年龄信息,用于垃圾回收目的。

  • 对象指针:指向栈帧中的锁记录(Lightweight Lock Record)。当一个线程尝试获取轻量级锁时,会在自己的栈帧中创建一个锁记录,然后将对象的 Markword 复制到这个锁记录中,并通过一定的操作将对象的 Markword 指向这个锁记录,这样就建立了对象与锁记录之间的联系,以便在后续的锁操作中进行相应的处理。

重量级锁状态

重量级锁是在多线程竞争激烈的情况下使用的一种相对较重的锁机制,通常会涉及到操作系统层面的互斥量等资源。当对象处于重量级锁状态时:

  • 锁标志位:变为 “10”,这是重量级锁的标识。

  • 偏向锁标志位:为 “0”,同样因为启用重量级锁时,偏向锁和轻量级锁机制都会暂时失效。

  • 分代年龄:还是存储对象的分的年龄信息,用于垃圾回收的操作。

  • 对象指针:指向互斥量(Mutex),也就是获取该对象锁的线程所对应的互斥量。当一个线程获取到重量级锁时,它会与这个互斥量建立联系,其他线程要获取该锁就必须等待这个互斥量被释放,也就是等待持有锁的线程完成操作并释放锁。

Markword 的更新与切换

在 Java 程序运行过程中,随着线程对对象的访问和锁状态的变化,Markword 的内容也会相应地更新和切换。

  • 偏向锁到轻量级锁的切换:当一个启用了偏向锁的对象首次遇到其他线程尝试获取锁时,偏向锁会向轻量级锁切换。具体操作包括将偏向锁标志位清零,将锁标志位设置为 “00”,并重新设置对象指针指向栈帧中的锁记录等。

  • 轻量级锁到重量级锁的切换:当轻量级锁在多线程竞争激烈的情况下,无法满足锁的需求时,会向重量级锁切换。这通常涉及到一些复杂的判断和操作,比如当多个线程同时尝试获取轻量级锁且竞争超过一定限度时,会将锁标志位设置为 “10”,将对象指针指向互斥量等操作,以启用重量级锁机制来处理激烈的多线程竞争情况。

Markword 在 Java 线程锁机制底层起着关键作用,它通过存储不同的锁状态信息以及相关标记,使得 Java 虚拟机能够根据对象的实际锁状态采取相应的处理方式,从而实现了高效的线程锁机制和对共享资源的有效管理。

Class Metadata
概念与作用

Class Metadata(类元数据)是关于类的各种信息的集合,它存储在方法区(在 Java 8 及以后版本中,方法区的实现为元空间)中。类元数据包含了类的结构信息、常量池、方法信息、字段信息等诸多内容,对于 Java 线程锁机制来说,它在某些方面也起到了重要作用。

与线程锁机制的关系

  • 类加载与初始化:在类加载和初始化过程中,类元数据被创建并存储在方法区。当线程首次访问一个类的实例或者调用一个类的静态方法时,会触发类的加载和初始化过程。这个过程涉及到对类元数据的操作,比如验证类的结构是否合法、解析类的符号引用等。在这个过程中,如果涉及到需要对类的实例或者静态方法进行锁操作,那么类元数据中的相关信息会影响到锁机制的实现。例如,对于静态方法的锁操作,锁对象通常是类对象,而类对象的相关特性(如是否已经初始化、是否存在锁竞争等)会根据类元数据来判断。

  • 锁的继承与覆盖:在 Java 中,子类可以继承父类的方法,并且可以对继承的方法进行覆盖。当涉及到锁操作时,子类继承的方法如果带有锁机制(如被 Synchronized 修饰),那么子类在继承和覆盖这些方法时,类元数据会记录这些锁相关的信息,并根据这些信息来决定在子类中如何实现锁机制。例如,子类继承了父类一个被 Synchronized 修饰的方法,在子类中执行该方法时,类元数据会告知是否需要重新获取锁、是否可以利用父类已有的锁等情况,从而影响到子类中锁机制的实现。

Java 线程锁机制底层的 Markword 和 Class Metadata 在对象的锁状态管理以及类相关的锁操作等方面起着至关重要的作用,它们相互配合,共同实现了 Java 多线程环境下高效、准确的锁机制。

jol-core分析Java对象布局工具库

jol-core 的作用

jol-core 是一个用于分析 Java 对象布局(Object Layout)的工具库,它在 Java 开发尤其是涉及到对内存布局、对象结构以及与底层相关的一些性能优化等场景中具有重要作用,具体如下:

1. 查看对象布局

  • 能够清晰地展示 Java 对象在内存中的具体布局情况,包括对象头(Object Header)、实例变量(Instance Variables)等各部分所占的字节数、存储的具体信息等。例如,它可以准确显示出对象头中的 Markword 部分的详细信息(如锁标志位、偏向锁标志位、分代年龄等在不同锁状态下的具体值),以及实例变量在内存中的排列顺序和对齐方式等。这对于深入理解 Java 对象在内存中的存储机制非常有帮助。

2. 理解对象的内存占用

  • 通过分析对象布局,开发人员可以准确得知一个对象在内存中到底占用了多少字节空间。这对于优化内存使用、发现潜在的内存浪费问题等方面至关重要。比如,在处理大量对象的应用场景中,如果能够精确知道每个对象的内存占用,就可以更好地规划内存分配策略,避免因内存过度占用导致的性能下降或内存不足等问题。

3. 研究锁机制相关的内存变化

  • 由于它能详细展示对象头的信息,所以在研究 Java 的线程锁机制与对象在不同锁状态下的内存表现时非常有用。例如,当一个对象从无锁状态转换为偏向锁、轻量级锁或重量级锁状态时,jol-core 可以清晰地呈现出对象头中 Markword 部分的变化情况,包括锁标志位、偏向锁标志位的改变以及相应指向信息(如指向持有偏向锁的线程 ID、栈帧中的锁记录、互斥量等)的变化,这有助于深入理解 Java 线程锁机制在底层内存层面的运作原理。

jol-core 的配合使用方法

以下以 Maven 项目为例,介绍如何在项目中引入并使用 jol-core 来分析对象布局:

1. 引入依赖

  • 在项目的 pom.xml 文件中,添加 jol-core 的依赖项:

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

这里的版本号可以根据实际情况进行更新,以获取最新的功能和修复。

2. 编写测试代码

  • 创建一个 Java 类用于测试,例如:

 import org.openjdk.jol.info.ClassLayout;
 import org.openjdk.jol.vm.VM;
 ​
 public class JolCoreTest {
     public static void main(String[] args) {
         // 创建一个示例对象
         Object obj = new Object();
 ​
         // 使用jol-core输出对象的内存布局信息
         System.out.println("VM.current().details():\n" + VM.current().details());
         System.out.println("ClassLayout.parseInstance(obj).toPrintable():\n" + ClassLayout.parseInstance(obj).toPrintable());
     }
 }

在上述代码中:

  • 首先创建了一个简单的 Object 对象作为示例。

  • 然后通过 VM.current().details() 语句可以获取当前虚拟机的一些详细信息,比如虚拟机的类型、版本、内存参数等,这对于了解整个运行环境有帮助。

  • 接着使用 ClassLayout.parseInstance(obj).toPrintable() 语句来获取并输出特定对象(这里是 obj)的内存布局信息,它会按照 jol-core 的格式详细展示对象头、实例变量等各部分的情况。

3. 运行测试代码并查看结果

  • 运行上述测试代码后,在控制台会输出相关信息。例如,对于 VM.current().details() 部分,可能会输出类似以下的内容:

 VM.current().details():
 # Running on Java HotSpot(TM) 64-bit Server VM, version 1.8.0_261
 # Compressed oop enabled: true
 # Compressed klass enabled: true
 # Heap region size: 1M
 # Max heap size: 2G
 # Initial heap size: 512M
 # Young generation size: 1536M
 # Eden space size: 1024M
 # Survivor space size: 256M
 # Tenured space size: 512M
 # Metaspace size: 256M

这展示了虚拟机的一些基本参数和特性。

  • 对于 ClassLayout.parseInstance(obj).toPrintable() 部分,可能会输出类似以下的内容:

 ClassLayout.parseInstance(obj).toPrintable():
 java.lang.Object object internals:
 OFF  SET   TYPE DESCRIPTION                    VALUE
  0   0   object header: mark 0x1(0000000000000001) (non-locked)
  8   0   object header: class 0x50010000 (java.lang.Object)
 16   0   (object end)

这里详细展示了 Object 对象的内存布局,包括对象头中 Markword 的具体值(这里显示为非锁定状态下的 0x1)以及指向类的信息等。

通过以上步骤,就可以在 Java 项目中引入并使用 jol-core 来分析对象布局,从而更好地理解 Java 对象在内存中的情况以及与线程锁机制等相关的底层原理。当然,除了 Maven 项目,在其他类型的项目(如 Gradle 项目等)中也可以通过相应的方式引入 jol-core 并进行类似的操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值