bigywater.tech
Java · 142

synchronized:从对象锁到字节码监视器

synchronized 在字节码里是什么、对象头里的锁状态怎么变,都可以直接看到。这篇用 javap 看指令,用 Unsafe 读对象头 mark word,在三个 JDK 版本上对比(锁的竞争与膨胀耗时已有专门的文章)。

2 次
synchronized 块里 monitorexit 的出现次数(正常 + 异常路径)
ACC_SYNCHRONIZED
synchronized 方法在字节码里的体现
00 → 10
无竞争时的轻量级锁,wait 之后膨胀成重量级锁(低 2 位)

块的字节码

实测 · 本机执行输出
  0: aload_0
  1: getfield      #7                  // Field lock:Ljava/lang/Object;
  4: dup
  5: astore_1
  6: monitorenter
  7: aload_0
  8: dup
  9: getfield      #13                 // Field n:I
  12: iconst_1
  13: iadd
  14: putfield      #13                 // Field n:I
  17: aload_1
  18: monitorexit
  19: goto          27
  22: astore_2
  23: aload_1
  24: monitorexit
  25: aload_2
  26: athrow
  27: return
  异常表:
    from    to  target type
    7    19    22   any
    22    25    22   any

方法的字节码

实测 · 本机执行输出
  方法:synchronized void method()
  flags: flags: (0x0020) ACC_SYNCHRONIZED
  指令: 0: aload_0 | 1: dup | 2: getfield      #13                 // Field n:I | 5: iconst_1 | 6: iadd | 7: putfield      #13                 // Field n:I | 10: return
  monitorenter 出现次数: 0

对象头里的锁

实测 · 本机执行输出
  刚创建:                 0x0000000000000001 低2位=01 无锁
  调用 hashCode() 之后:   0x000000610455d601 低2位=01 无锁
  synchronized 块内:      0x000000016bd56ac0 低2位=00 轻量级锁(栈上)
  离开 synchronized 之后: 0x0000000000000001 低2位=01 无锁
  块内调用过 wait(10):    0x0000007c41064c42 低2位=10 重量级(monitor)
  离开块之后:             0x0000007c41064c42 低2位=10 重量级(monitor)
  (JDK 27 同一程序:)
  刚创建:                 0x0017ac0000000001 低2位=01 无锁
  调用 hashCode() 之后:   0x0017ac45e62af801 低2位=01 无锁
  synchronized 块内:      0x0017ac0000000000 低2位=00 轻量级锁(栈上)
  离开 synchronized 之后: 0x0017ac0000000001 低2位=01 无锁
  块内调用过 wait(10):    0x0017aec3226a3002 低2位=10 重量级(monitor)
  离开块之后:             0x0017aec3226a3002 低2位=10 重量级(monitor)
  (JDK 17 同一程序:)
  刚创建:                 0x0000000000000001 低2位=01 无锁
  调用 hashCode() 之后:   0x00000014dad5dc01 低2位=01 无锁
  synchronized 块内:      0x000000016b0a2ae0 低2位=00 轻量级锁(栈上)
  离开 synchronized 之后: 0x0000000000000001 低2位=01 无锁
  块内调用过 wait(10):    0x0000007862d1c542 低2位=10 重量级(monitor)
  离开块之后:             0x0000007862d1c542 低2位=10 重量级(monitor)

mark word 的读取用了 sun.misc.Unsafe(编译时加 -XDignore.symbol.file,JDK 27 加 --sun-misc-unsafe-memory-access=allow),这只是为了观察,不要在业务代码里这样做。

异常与锁释放

实测 · 本机执行输出
  块内 holdsLock = true
  捕获 boom;块外 holdsLock = false
  另一个线程拿到了锁,等待 1695 µs

分步演示

synchronized 实现
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 17.0.20、21.0.12、27(mark word 对比);javap 用 JDK 21
    • 没有验证:偏向锁(JDK 18 起已移除,本篇没有在 17 上用 -XX:+UseBiasedLocking 单独验证)、锁在 GC 或时间到后降级、-XX:LockingMode 参数在各版本的取值、竞争下的自旋与等待队列细节(见已有的锁膨胀文章)
    京ICP备2026067502号-1京公网安备11010502063487号