尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

markword在高并发场景下变化剖析

markword在高并发场景下变化剖析
📅 发布时间:2026/7/5 9:12:24

markword在高并发场景下变化剖析

  • 前言
  • markword在高并发场景下变化剖析
    • 64位 markword的内存复用基准架构
    • 第一次内存置换:轻量级锁的“栈帧置换”(Stack Displacement)
      • 1. 场景与触发时机
      • 2. 底层置换机制
      • 3. OpenJDK 8核心源码解构
    • 第二次内存置换:重量级锁膨胀的“堆/本地内存置换”(Monitor Inflation Displacement)
      • 1. 场景与触发时机
      • 2. 底层置换机制
      • 3. OpenJDK 8核心源码解构
    • 第三次内存置换:GC 存活对象疏散的“转发指针置换”(GC Forwarding Displacement)
      • 1. 场景与触发时机
      • 2. 底层置换机制
      • 3. OpenJDK 8核心源码解构
    • 总结:系统视角下的三次内存置换精髓

前言

本文旨在记录近期研读Java源码的学习心得与疑难问题。由于个人理解水平有限,文中内容难免存在疏漏,恳请读者不吝指正。

markword在高并发场景下变化剖析

在 HotSpot 虚拟机(OpenJDK 8)中,每个 Java 对象头都包含一个关键的Mark Word(在源码中体现为markOop)。在 64 位架构下,markword占据 64 位的内存空间。由于这 64 位空间需要同时承载对象的哈希码、分代年龄、锁状态标志以及线程持有信息,JVM 设计了一种高度复用(Overloaded)的内存结构。

当对象生命周期发生演进或在高并发场景下遭遇激烈的锁竞争时,markword的常规数据结构会被强行打破,其内部原有的元数据(如identity_hashcode、age)会被剥离并转移,腾出空间存放原生指针。这个过程在底层被称为Displacement(置换)。

以下是 markword在底层生命周期与高并发下发生的三次最核心的“内存置换”。


64位 markword的内存复用基准架构

在理解置换之前,需明确其基础内存布局。通过下表可以直观看出,高 62 位在不同状态下承载的内容有着根本性的置换:

锁状态 (Lock State)高 62 位 (62 Bits Layout)偏向标志 (1 Bit)锁标志 (2 Bits)内存实际承载位置与说明
无锁 (Neutral)25位未使用31位identity_hashcode1位未使用4位age
偏向锁 (Biased)54位Thread ID2位Epoch1位未使用4位age
轻量级锁 (Lightweight)指向线程栈帧 Lock Record的原生指针— (00)00置换至当前线程栈
重量级锁 (Heavyweight)指向Native Heap ObjectMonitor的原生指针— (10)10置换至 C++ 堆内存
GC 转发 (Forwarded)指向To 空间/晋升目标空间新对象的原生指针— (11)11置换至新对象头部(原旧对象头报废)

第一次内存置换:轻量级锁的“栈帧置换”(Stack Displacement)

1. 场景与触发时机

当关闭偏向锁,或者偏向锁由于多线程交替执行导致撤销后,对象进入无锁状态(001)。此时,若有线程尝试进入同步块(synchronized),且当前无激烈竞争,JVM 会选择轻量级锁降低系统开销。

2. 底层置换机制

为了将整个 64 位的 markword空出来存放指向当前线程栈的指针,JVM 必须把对象当前包含identity_hashcode和age的无锁 markword备份。

  • 写出:线程在自己的执行栈帧(Stack Frame)中分配一个BasicObjectLock记录(即 Lock Record)。将对象头此时的无锁 markword拷贝到 Lock Record 内部的_displaced_header字段中。
  • 写入:线程通过 CPU 的原子CAS (Compare-And-Swap)指令,尝试将对象头自身的 markword覆写为“指向该 Lock Record 的内存首地址”,并将锁标志位置换为00。

3. OpenJDK 8核心源码解构

在解释器模式下,该置换动作的核心逻辑位于bytecodeInterpreter.cpp的_monitorenter节点下:

// share/vm/interpreter/bytecodeInterpreter.cppCASE(_monitorenter):{// 从操作数栈获取锁对象 (Oop)oop lockee=STACK_OBJECT(-1);CHECK_NULL(lockee);// 在当前线程栈帧中寻找一个空闲的锁记录 (Lock Record)BasicObjectLock*limit=istate->monitor_base();BasicObjectLock*most_recent=(BasicObjectLock*)istate->stack_base();BasicObjectLock*lock=NULL;...// 获取对象当前最新状态的 Mark WordmarkOop mark=lockee->mark();// 确认为非偏向锁且处于无锁(Neutral)状态if(mark->has_no_bias_in_cube()){// 【内存置换:第一步】将对象原有的 markword(包含哈希码、年龄) 暂存到栈帧锁记录的指定字段中// 官方命名十分直白:set_displaced_header(设置被置换的头部)lock->set_displaced_header(mark);// 【内存置换:第二步】通过原子 CAS 操作进行置换// 期望值:当前的 mark// 新值:指向当前栈帧 Lock Record 的指针 (最低两位由于内存对齐为 00)// 地址:lockee->mark_addr()if(Atomic::cmpxchg_ptr(lock,lockee->mark_addr(),mark)==mark){// 置换成功!意味着当前线程无视竞争,成功用栈指针替换了原对象头,获取了轻量级锁if(PrintBiasedLockingStatistics)return;}else{// CAS 失败,说明在置换期间别的线程插足了,触发锁升级/膨胀流程// CALL 慢速锁分配器...}}...}

第二次内存置换:重量级锁膨胀的“堆/本地内存置换”(Monitor Inflation Displacement)

1. 场景与触发时机

在轻量级锁状态下(00),若发生多线程高并发激烈竞争(自旋失败),或者某个持有锁的线程调用了Object.wait()方法,锁就必须膨胀为依赖底层操作系统的重量级锁(10)。

2. 底层置换机制

升级为重量级锁意味着对象头必须再次让出全部空间,改为存放一个指向 C++ 堆内存中ObjectMonitor结构体的原生指针。

  • 写出:锁膨胀器(Inflater)从 Native Heap 中分配或复用一个ObjectMonitor。它必须读取当前正在持有轻量级锁的线程栈,将之前第一次置换时暂存在那里的displaced_header(无锁 Mark Word)取出来,再次置换并持久化到ObjectMonitor的_header字段中。
  • 写入:利用 CAS 将对象的 markword设置为膨胀中标志INFLATING锁定现场,接着最终改写为指向该ObjectMonitor的地址,并将标志位置换为10。

3. OpenJDK 8核心源码解构

该过程的核心逻辑位于synchronizer.cpp文件的ObjectSynchronizer::inflate方法中:

// share/vm/runtime/synchronizer.cppObjectMonitor*ATTRObjectSynchronizer::inflate(Thread*Self,oop object){// 保持自旋死循环,直到膨胀置换成功for(;;){markOop mark=object->mark();assert(!mark->has_bias_pattern(),"invariant");// CASE 1: 已经是重量级锁状态 (标志位为 10),说明其他线程完成了置换,直接返回if(mark->has_monitor()){ObjectMonitor*m=mark->monitor();returnm;}// CASE 2: 锁正在被其他线程实施膨胀中,当前线程轻量级自旋等待其置换完成if(mark==markOopDesc::INFLATING()){ReadStableMark(object);continue;}// CASE 3: 当前是轻量级锁状态 (Stack-locked) —— 核心冲突高发区if(mark->has_locker()){// 【本地内存分配】从系统的 Native Heap 分配一个 ObjectMonitor 节点ObjectMonitor*m=omAlloc(Self);m->Recycle();m->_Responsible=NULL;m->_recursions=0;m->_spinDuration=ObjectMonitor::Knob_SpinLimit;// 【内存置换:第一步】提取持有轻量级锁的线程栈中之前保存的 displaced mark wordmarkOop dmw=mark->displaced_mark_helper();// 【内存置换:第二步】将这个最初的无锁元数据,再次转移存储到 ObjectMonitor 的 _header 中m->set_header(dmw);// 设置重量级监视器的拥有者为原轻量级锁的 Lock Record 栈地址m->set_owner(mark->locker());m->set_object(object);// 【临界原子置换】通过 CAS 将对象头置换为特定的 INFLATING 状态标志if(Atomic::cmpxchg_ptr(markOopDesc::INFLATING(),object->mark_addr(),mark)!=mark){// 置换失败则释放申请的 Monitor 并重试omRelease(Self,m,true);continue;}// 【内存置换:第三步】最终收尾置换// 装配带有重量级锁标志(10)的 ObjectMonitor 原生指针,覆写到对象头中object->release_set_mark(markOopDesc::encode(m));returnm;}// CASE 4: 处于无锁状态下直接请求重量级锁(如直接调用了 hashcode 或 wait)if(mark->is_neutral()){ObjectMonitor*m=omAlloc(Self);m->Recycle();// 直接将当前的无锁 markword塞入 Monitor 的 _header 中m->set_header(mark);m->set_owner(NULL);m->set_object(object);...// 通过 CAS 彻底置换if(Atomic::cmpxchg_ptr(markOopDesc::encode(m),object->mark_addr(),mark)!=mark){...// 失败处理}returnm;}}}

第三次内存置换:GC 存活对象疏散的“转发指针置换”(GC Forwarding Displacement)

1. 场景与触发时机

在垃圾回收(如 Minor GC、G1 的 Evacuation 阶段)发生时,GC 线程会扫描出所有的存活对象。为了解决内存碎片问题,GC 必须将这些存活对象搬迁(疏散)到新的内存区域(如 Survivor 空间或老年代)。

2. 底层置换机制

在高并发的多 GC 线程并行复制场景下,同一个旧对象可能同时被两个 GC 线程扫描到并尝试复制。为了确保一致性,并让所有指向旧对象的引用能够感知到新对象的存在:

  • 写出:GC 线程在 To 空间分配一块新内存,将旧对象的全部内容(包含当前的 Mark Word)原封不动拷贝过去。
  • 写入:随后,GC 线程利用 CAS 尝试强行将旧对象的 markword整体擦除替换。替换后的内容为:指向新对象内存首地址的原生指针,同时将其锁标志位硬编码置换为11(已被 GC 标记转发)。
  • 意义:后续其他引用指向旧对象时,只要读到 markword的最低两位是11,就能立刻通过解引用该 markword中的Forwarding Pointer(转发指针)找到新对象。

3. OpenJDK 8核心源码解构

这个极度底层的并发置换逻辑存在于oop.inline.hpp对象的 inline 方法中:

// share/vm/oops/oop.inline.hpp// 基础单线程/独占 GC 置换逻辑inlinevoidoopDesc::forward_to(oop p){assert(Universe::heap()->is_in_reserved(p),"forwarding to something not in heap");// 【置换值构造】将新分配出来的对象内存首地址 p,编码转换成一个 markOop// 底层会将其最后两位置换为 '11'markOop mp=markOopDesc::encode_pointer_as_mark(p);assert(mp->decode_pointer()==p,"encoding must be reversible");// 【核心置换】直接覆写旧对象的 markword空间,将其变为 Forwarding Pointerset_mark(mp);}// 多线程并行高并发 GC(如 G1/Parallel Scavenge)在实施对象搬迁竞争时的原子置换inlineoop oopDesc::cas_forward_to(oop p,markOop compare){assert(Universe::heap()->is_in_reserved(p),"forwarding to something not in heap");// 将搬迁后的新对象首地址编码为带有 11 标志位的 markOop 转发指针markOop mp=markOopDesc::encode_pointer_as_mark(p);// 【并发原子置换】利用底层 CPU 提供的锁总线/缓存行指令进行 CAS// 期望旧对象头依然是 compare// 目标置换为指向新地址的 mpmarkOop old=(markOop)Atomic::cmpxchg_ptr(mp,mark_addr(),compare);if(old==compare){// 返回 NULL 代表置换成功:当前 GC 线程赢得了竞争,成功完成了对该对象的疏散和转发关联returnNULL;}else{// 返回真实的 old 代表置换失败:说明另一个 GC 线程动作更快,已经把旧对象的 markword// 置换成了它复制的新对象地址。当前线程应放弃当前复制,去读取 old 里别人置换好的新对象地址returnold;}}

总结:系统视角下的三次内存置换精髓

从底层内存管理的本质来看,HotSpot 虚拟机的这三次 markword置换体现了极端紧凑的空间借调思想:

  1. 轻量锁置换是向当前线程的执行栈借用空间。它建立了一种“对象头指向栈,栈内保存原头”的父子双向绑定,用于在低竞争下快速识别锁归属。
  2. 重量锁置换是向Native Heap 堆内存借用空间。它解除了与特定线程栈的物理绑定,将原对象头托付给全局的ObjectMonitor,从而能够利用更复杂的等待队列(_WaitSet/_EntryList)和底层操作系统的Mutex Lock来应对高并发大厦将倾时的剧烈冲击。
  3. GC 转发置换则是向未来的新内存对象借用空间。它直接将旧对象的生存尊严(markword空间)彻底剥夺,使其降级为一个纯粹的“路标指针”(Forwarding Pointer),以此在并发垃圾回收的洪流中,引导所有的存活引用完成地址的平滑迁移。

相关新闻

  • BSCCompiler静态代码分析:使用clang-tidy提升代码质量的完整指南
  • CTinspector企业级部署方案:大规模集群下的流量检测架构设计
  • TestNG插件离线安装全攻略:内网环境下的Java自动化测试部署

最新新闻

  • 垂直氮化镓技术:高压电力电子的未来
  • 豆包四大框架拆解:对话理解、角色驱动、知识增强与工具协同
  • 西威变频器主板底座设计差异与维修要点解析
  • 深入解析SSD与内存卡的核心原理与性能差异
  • 大华智能物联平台默认口令漏洞:从Token机制到内网渗透的实战复现
  • RK3588核心板:高性能AIoT开发全解析

日新闻

  • 基于YOLOv12的番茄成熟度智能检测系统开发
  • 终极RimWorld模组管理指南:用RimSort告别模组冲突烦恼
  • AI Agent框架开发:从理论到实践的完整指南

周新闻

  • 基于YOLOv12的番茄成熟度智能检测系统开发
  • 终极RimWorld模组管理指南:用RimSort告别模组冲突烦恼
  • AI Agent框架开发:从理论到实践的完整指南

月新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号