栈帧与轻量级锁记录有什么关联

66 阅读3分钟

前言: Java 虚拟机(JVM)的运行机制里,“栈帧”(Stack Frame)和 “锁记录”(Lock Record)是两个至关重要的概念,它们在多线程编程和方法执行过程中扮演着关键角色。下面我们将深入剖析这两个概念,帮助大家更好地理解 Java 程序的运行原理。

栈帧:方法执行的临时工作台

栈帧是线程执行方法时,在虚拟机栈中创建的一块内存区域。你可以把它简单理解为 “方法执行时的临时工作台”,主要用于存储当前方法的局部变量、操作数栈、方法返回地址等信息。

栈帧与线程的关系

每个线程都拥有独立的虚拟机栈,这一特性保证了线程之间互不干扰。虚拟机栈由多个栈帧构成,当线程调用一个方法时,会创建一个新的栈帧并将其压入栈顶;当方法执行完毕,对应的栈帧就会从栈中弹出。

举个例子,当线程依次执行 A() -> B() -> C() 方法时,虚拟机栈会依次压入 ABC 方法对应的栈帧。首先 C 方法执行完毕,其对应的栈帧弹出;接着 B 方法执行完毕,B 方法的栈帧也随之弹出,依此类推。

轻量级锁与锁记录

轻量级锁是 Java 中一种高效的锁机制,适用于竞争不太激烈的场景。在轻量级锁的实现过程中,“锁记录” 发挥着重要作用。

锁记录的作用

当线程尝试获取轻量级锁时,JVM 会在当前正在执行方法的栈帧中创建一块专门的内存区域,即 “锁记录”。锁记录主要存储以下两方面信息:

  • 对象头的 Mark Word 副本:这是锁加锁前对象头的原始状态,相当于一个 “备份”,方便后续解锁时恢复对象状态。
  • 指向锁对象的指针:该指针明确了这个锁记录是为哪个对象加锁所用。

可以把锁记录形象地比喻成线程在自己的 “工作台” 上放置的一张 “便签”,上面记录着 “我要锁的对象原来是什么样子”。

解锁时如何找到锁记录

当线程执行完同步代码块需要解锁时,会从当前栈帧中找到之前创建的 “锁记录”。然后依据锁记录里的 “Mark Word 副本”,通过 CAS(Compare-And-Swap)操作将对象头恢复成加锁前的状态。这就好比看着便签上的 “原始样子”,把对象恢复到最初状态。

通俗比喻帮助理解

为了更直观地理解栈帧和锁记录,我们可以用一个形象的比喻:

  • 线程的虚拟机栈:就像一个堆叠的抽屉柜,每个抽屉代表一个栈帧。
  • 执行方法:如同打开一个新抽屉(创建栈帧),在里面处理相关数据。
  • 轻量级锁的 “锁记录” :相当于在当前打开的抽屉里放一张便签,记录 “我要锁的东西原来长什么样”。
  • 解锁:看着便签上的记录,把锁的东西恢复原样,然后关上抽屉(方法执行完,栈帧弹出)。

总结

“栈帧” 是线程执行方法时的临时内存区域,而 “锁记录” 是轻量级锁机制在栈帧中临时存放锁信息的地方。线程从自己的栈帧中找锁记录,就像从自己正在使用的抽屉里找之前记的便签,方便且安全(因为栈帧是线程私有的,不会被其他线程干扰)也体现了线程之间的资源共享。