{"name":"JAVA内存模型","id":"编程语言-JAVA-JVM-JAVA内存模型","content":"# JAVA 内存模型\n\n硬件的缓存与指令重排以性能为先，不保证跨线程的可见性与有序性——JMM 以什么最小规则，为程序员提供确定、可推理的并发语义？\n\n## 为什么需要 Java 内存模型\n\n### 并发的第一性问题\n\n并发的真问题是：\n\n> **一个线程对共享状态的修改，是否、何时、以何种顺序，被另一个线程观察到**——抽象为可见性、有序性、原子性。\n\n若无统一约束，不同 CPU 架构、缓存一致性协议、编译器优化策略，会让同一段 Java 程序在不同机器上语义不同。\n\n### 三层重排：源码顺序为何 ≠ 执行顺序\n\nJMM 要驯服的，是破坏\"程序员直觉顺序\"的重排——它来自三个层，各为性能而设，单看都不改变单线程语义：\n\n| 层 | 重排来源 | 破坏的直觉 | 关联 |\n| --- | --- | --- | --- |\n| **编译期** | 编译器优化：寄存器驻留、指令调度、死代码消除 | 源码顺序 ≠ 执行顺序；变量不重读主存 | 编译优化 / JIT |\n| **执行期（核内）** | CPU 乱序执行 / 推测执行 | 执行顺序 ≠ 程序顺序 | [处理器体系架构.md § 乱序执行](/计算机系统/程序结构和执行/处理器体系架构.md) |\n| **访存期（跨核）** | 内存系统：store buffer（写侧）+ 失效队列（读侧） | 写不立即对他核可见、跨核观察顺序不一 | [存储器层次结构.md § MESI](/计算机系统/程序结构和执行/存储器层次结构.md)、[多处理机系统.md](/操作系统/多处理机系统.md) |\n\n三层单看都\"正确\"（不改单线程语义），叠加后却**无法为高级语言提供确定的并发语义**——同一段 Java 程序在不同机器上可能有不同结果。\n\n### JMM 的价值：从硬件混沌到语言秩序\n\n> 硬件提供\"能做到什么\"，内存模型定义\"程序员能依赖什么\"。\n\nJMM 的任务是**在性能与可推理性间划线**：\n\n* 允许编译器与硬件做**不影响单线程语义的优化**（重排、缓存、推测）\n* 但通过 happens-before 等抽象，给程序员**确定的跨线程可见性与有序性保证**\n* 用内存屏障等机制，把语言级保证**向下兑现到具体硬件**\n\n这也解释了 `volatile` 为何必须定义在**语言层**：一个关键字要同时压制上述三层重排——禁 JIT 重排（编译期）、插 CPU 屏障（执行期）、由屏障刷 store buffer / 排空失效队列（访存期）。单靠任一层的机制都盖不全，唯有语言级契约能横跨三层统一约束。\n\n这就是为什么 JMM 是\"契约\"而非\"实现\"——它不规定 JVM 用哪种 fence 指令，只规定\"程序员写了 `volatile` 后能依赖什么\"，由 JVM 根据底层硬件（x86 的 TSO、ARM 的弱序）选择兑现方式。\n\n> 没有内存模型，程序员要理解每种 CPU 的乱序规则、每种缓存协议的可见性窗口，Java 的\"一次编写、到处运行\"将破产。\n\n## JMM 的抽象模型\n\n### JMM 的核心契约：一条分段保证\n\n在展开任何机制前，必须先说清 JMM 到底给程序员**承诺了什么**。JMM 不承诺\"所有程序都可推理\"，而是**划一条线**：\n\n| 程序 | 定义 | JMM 的承诺 |\n| --- | --- | --- |\n| **正确同步（线内）** | 所有 SC 执行都无数据竞争 | **最强保证**：所有执行表现为顺序一致，可用单线程直觉推理 |\n| **有数据竞争（线外）** | 两线程访问同一变量、至少一写、且不由 happens-before 排序 | **最低兜底**：读只看到真实写过的值、不落入 UB（有界，而非灾难；见 happens-before 节） |\n\n\"正确同步\"的判据不是\"实际执行无竞争\"，而是**先假设程序按 SC 运行**，在这个最直观、执行集合可枚举的世界里检验是否无竞争——以此避开\"竞态程序执行集合本身依赖内存模型\"的循环。\n\n这条线为什么这样画：\n\n| 候选 | 问题 |\n| --- | --- |\n| 全程顺序一致 | 禁掉缓存 / 乱序 / 重排，太慢 |\n| 纯弱模型 | 允许凭空产生（out-of-thin-air）值，破坏安全性 |\n| **分段契约** | 好程序好到极致，坏程序坏得有底线 |\n\n顺序越强，代价越高，happens-before **用性能换来的因果保证**\n\n线外的\"有界\"是 Java 与 C++ 的分水岭，背后是安全性 vs 性能的根本取舍。第一部分（SC-DRF）是语言无关的通用契约（C++、Go、Rust 同构），其在\"内存模型光谱\"中的位置见 [/编程语言/并发编程.md](/编程语言/并发编程.md)。\n\n### 主内存与工作内存：可见性问题的直观模型\n\n> **心智脚手架，非形式化地基。** 它解释可见性问题的\"现象\"，不定义\"何种行为合法\"——后者是下一节 happens-before 的职责。\n\n模型：**主内存**（线程共享）+ **工作内存**（线程私有的变量副本）；线程只能读写自己的工作内存，通信必须经主内存中转——副本不同步即产生可见性问题。\n\n* **工作内存不是 JVM 的内存区域**，而是对 CPU 缓存 / 寄存器 / 编译器暂存的抽象。\n* \"改副本 → 刷回主存\"的 flush / invalidate 叙事，只是 happens-before 的**直观投影**，非独立机制。\n* 它源自**早期 JMM**，已被 JSR-133（2004）以 happens-before 偏序取代——故此处仅作直觉入门，不作依据。\n\n### happens-before：JMM 的灵魂\n\n#### happens-before 的本质\n\nhappens-before **不是时间先后关系**，而是：\n\n> **可见性与有序性的因果约束关系（Partial Order）**\n\n含义只有一个：\n\n> 如果 A happens-before B，\n> 那么 A 的结果 **对 B 可见**，并且 A 的执行顺序 **排在 B 之前**。\n\n如果两个操作之间 **不存在 happens-before 关系**：\n\n* JVM 可以自由重排序\n* 读取到由某线程写入过的任一值（含默认零值）都是合法的\n\n这个\"核心契约用一条分段保证\"节所划线外的有界语义——仍保证不落入 OOTA/UB。\n\n> **因果方向（纠偏）**：不是”先有主/工作内存交互，再有 happens-before 约束”；\n> 而是 happens-before 为地基，缓存的刷新与失效只是它在硬件上的一种兑现方式。\n\n#### happens-before 的天然规则\n\n这些规则看似零散，实则收敛于**一条生成原理**：\n\n> **跨线程因果只有一个来源——成对的同步动作：一个\"发布\"，一个\"接收\"。**\n> 一方做**发布**动作，把此前的所有写公开；另一方对**同一媒介**做**接收**动作，观察到该发布，从而收取这些写。锁只是其中一个实例，volatile、线程启停亦然。\n\n**① 造边（唯一原理）：发布 → 接收（即 release/acquire 语义）**\n\n作用于**同一媒介**的\"发布动作\"happens-before 对它的\"接收动作\"。JLS 列的 5 条同步边，都是这一模式的实例：\n\n| 媒介 | 发布（release 侧） | 接收（acquire 侧） |\n| --- | --- | --- |\n| 锁 | unlock | lock（同一锁） |\n| volatile 变量 | 写 | 读（同一变量） |\n| 线程对象 | 父线程 `start()` | 子线程首个动作 |\n| 线程对象 | 线程末动作 | `join()`/`isAlive` 检测到终止 |\n| 中断标志 | `interrupt()` | 被中断线程检测到中断 |\n| 变量初值 | 系统写默认值（0/false/null） | 每个线程首个动作 |\n\n判据由此自明：**只有成对的同步动作、作用于同一媒介时才造边**——普通读写不是同步动作，故彼此无边、可被自由重排；作用于不同媒介（不同锁、不同 volatile 变量）也不造边。最后一行是\"读至少看到默认值、不落入 OOTA\"的规范来源。\n\n**② 线程内定序：程序顺序**——单线程内，前一个动作 hb 后一个动作。\n\n**③ 连边成链：传递性**——A hb B、B hb C ⟹ A hb C，把离散的边闭合成偏序。\n\n（另有一条特例直接边：对象构造完成 → 其 `finalize` 开始。）\n\n#### as-if-serial：happens-before 的单线程对偶\n\nas-if-serial 与 happens-before 是**一对重排边界**，共同框定\"JMM 允许哪些重排\"：\n\n| | 作用范围 | 底线 |\n| --- | --- | --- |\n| as-if-serial | 单线程内 | 结果\"看起来像顺序执行\"，线程自己观察不到被重排 |\n| happens-before | 跨线程 | 同步点之外无可见性 / 顺序保证 |\n\n> **单线程语义下，执行结果必须\"看起来像是顺序执行的\"**——它同时约束编译器与 CPU，保证单线程世界的确定性。\n\n关键在于其**边界**：as-if-serial 只在单线程成立，一旦跨线程即失效。单线程的确定性会沿原子性、可见性、有序性三个方向各自破裂。\n\n### happens-before 不是 Java 的发明：一条跨域原理\n\nhappens-before 并非 JMM 独创，而是 Lamport 1978 年为**分布式系统**定义的因果偏序，JMM 是它在单机并发的一次投影。同一原理在本库三个域出现：\n\n> **在没有全局时钟的世界里，因果偏序是唯一可靠的\"顺序\"；\n> 而\"正确\"的标准，是执行结果等价于某个合法的串行/顺序全序。**\n\n| 域 | 偏序的载体 | \"正确\"的判据（等价于某串行/顺序全序） |\n| --- | --- | --- |\n| JMM | happens-before | SC-DRF：正确同步的程序表现为顺序一致 |\n| 分布式 | Lamport happens-before / 逻辑时钟 | 线性一致 / 因果一致 |\n| 数据库事务 | 冲突可串行化的前趋图偏序 | 可串行化 |\n\n**顺序越强，性能代价越高**——需付性能去换顺序：\n\n| 域 | 最强（最可推理，代价最高） | 中间档 | 最弱（最快，最难推理） |\n| --- | --- | --- | --- |\n| JMM | 顺序一致 SC | SC-DRF（Java 实际取值） | 纯弱模型 / Plain |\n| 分布式 | 线性一致 | 因果一致 | 最终一致 |\n| 事务 | 可串行化 | RR / RC | 读未提交 |\n\n这是同一取舍在不同规模上的实例：都用\"因果偏序\"表达秩序，用\"等价于某全序\"定义正确，用\"放松顺序\"换取性能。\n\n* 分布式因果序与逻辑时钟（Lamport）：见 [/软件工程/架构/系统设计/分布式/分布式.md](/软件工程/架构/系统设计/分布式/分布式.md)\n* 可串行化作为正确性北极星：见 [/中间件/数据库/数据库系统/事务管理/事务.md](/中间件/数据库/数据库系统/事务管理/事务.md)\n* 内存模型光谱（语言无关）：见 [/编程语言/并发编程.md](/编程语言/并发编程.md)\n\n## JMM 的并发三特性\n\n| 特性 | 失效面（单线程哪个前提崩了） | JMM 是否直接保证 | 修复手段 |\n| --- | --- | --- | --- |\n| **原子性** | 复合操作中途被介入 | 仅单次访问；64 位 long/double 非 volatile 允许拆分（撕裂） | synchronized / CAS |\n| **可见性** | 写停在本地未公开 | 是（经 happens-before） | volatile / synchronized（unlock 刷主存）/ final（构造安全发布） |\n| **有序性** | 重排被他线程观察到 | 是（经 happens-before） | 建立 happens-before |\n\n关键区分：**可见性与有序性由 happens-before 统辖，原子性是独立轴**——这也是为什么原子性是三者里唯一大部分落在 JMM 之外的一个：JMM 只保证单次访问原子，复合操作的原子性需由锁 / CAS 自建。\n\n## JMM 的实现手段\n\n> JMM 只定义\"程序员能依赖什么\"，JVM 用现实工具兑现。三种工具，一条兑现链：**语义（hb 边）→ 字节码 → JVM → 硬件**。\n\n| 工具 | 语义本质 | 字节码 / 标志 | 兑现层 | 适用 |\n| --- | --- | --- | --- | --- |\n| **volatile** | 建立写→读的 hb，禁相关重排 | `ACC_VOLATILE` | JVM 插屏障 → 硬件 fence / lock | 状态标志、单写多读，不含复合不变式 |\n| **synchronized** | 临界区全序视图（互斥 + hb 封闭） | `monitorenter/exit` | 对象监视器 → OS/CPU 原子指令 | 复合操作、复合不变式 |\n| **内存屏障** | 面向 JVM，非程序员——把上两者的语义**兑现到硬件** | — | LoadLoad / StoreStore / LoadStore / StoreLoad | （JVM 内部）|\n\nsynchronized 的价值不止互斥，还有**内存语义的边界封装**：进出临界区即一对发布/接收。四种屏障是这条链的最底层——volatile、锁的 hb 边，最终都由 JVM 按目标 ISA 选插哪几种屏障来落地。\n\n## 工程视角：如何\"用对\"JMM\n\n**共享可变数据的每次访问，是否被 happens-before 边覆盖？** 有 → 线内、可推理；无 → 数据竞争。\n\n**JMM 的边界（它不解决）**：复合操作的原子性、业务不变式、高层并发策略——都在 happens-before 之上，需用锁 / CAS / 设计自行保证。\n\n> JMM 是并发世界的物理定律，不是业务逻辑的守护神：它保证\"写了同步就可推理\"，不保证\"你的同步逻辑对\"。\n\n具体工具选型（volatile / synchronized / 原子类 / 不可变对象 / 并发容器 的适用与反模式）见 [/编程语言/JAVA/JAVA并发编程/JAVA并发编程.md](/编程语言/JAVA/JAVA并发编程/JAVA并发编程.md)。\n\n## 演进与对比视角\n\n### 内存模型的三次迭代：一场未完的战争\n\n一条主线贯穿三代：**竞态安全（禁 OOTA）与优化自由（允许一切合法重排）的矛盾，每代用更精确的形式化推进一步，至今未根除。**\n\n| 里程碑 | 解决了上代什么 | 埋下 / 遗留的洞 |\n| --- | --- | --- |\n| **老 JMM（1995）** | 首次定义并发语义（主内存/工作内存操作式模型） | **形式化不自洽**：既太强（禁合法优化）又太弱（final 失效、DCL 坏）→ Pugh 判 “fatally flawed”，不可补丁只能重写 |\n| **JSR-133（2004）** | 换 happens-before 偏序作地基；立 SC-DRF 正面契约；修 volatile（发布屏障）与 final（freeze） | 竞态语义的**因果性形式化（§17.4.8）重蹈覆辙**——又太强（禁掉合法优化，HotSpot 实际不遵守）又不干净（OOTA 未封死） |\n| **JMM9（进行中）** | 目标：干净禁 OOTA、给 VarHandle 弱访问模式补形式语义、重审 64 位原子性 | **根本矛盾未解、至今未定稿**；API（VarHandle）已随 Java 9 发布，但其形式模型缺席 |\n\n> 三代都在”安全 vs 优化”的张力上博弈，且**形式化越精确，矛盾暴露得越清楚**。\n\n### 两条路径：有界 vs 未定义\n\nJMM 的第二部分保证（racy 程序仍有界）是 Java 与 C/C++ 的**根本分水岭**，背后是两种设计立场：\n\n| 立场 | 对”写错的并发程序”给什么 | 动机 |\n| --- | --- | --- |\n| **Java** | 有界语义（读到真实写过的值、类型/内存安全不破） | 沙箱安全、可调试、防安全管理器绕过 |\n| **C/C++** | 未定义行为（UB） | 实现简单、性能极致，竞态 = 灾难 |\n\n这不是”Java 更安全”的简单结论，而是**在语言定位上的根本取舍**：\n\n* Java 承诺”即使程序有 bug，JVM 沙箱不被突破”（安全性作为不可协商底线）\n* C++ 承诺”只要程序正确，给你最薄的抽象层”（零成本抽象作为第一原理）\n\n### 跨语言的共同点：SC-DRF\n\n第一部分保证（DRF→SC）是语言无关的，Go、Rust、Swift 都采用同构契约。差异只在第二部分——Rust 通过类型系统在编译期**大幅减少竞态**（但 unsafe 块仍可能有），Go 介于 Java 与 C++ 之间。\n\n见 [/编程语言/并发编程.md](/编程语言/并发编程.md)。\n\n## 关联内容（自动生成）\n\n- [/编程语言/JAVA/JAVA并发编程/JAVA并发编程.md](/编程语言/JAVA/JAVA并发编程/JAVA并发编程.md) 介绍了Java并发编程的整体概念，与JMM作为Java并发语义基础密切相关\n- [/编程语言/JAVA/JAVA并发编程/并发集合.md](/编程语言/JAVA/JAVA并发编程/并发集合.md) 并发集合的实现依赖于JMM提供的内存模型和同步机制，是JMM在实际开发中的重要应用\n- [/编程语言/JAVA/JAVA并发编程/并发工具类.md](/编程语言/JAVA/JAVA并发编程/并发工具类.md) Java并发工具类的实现基于JMM提供的同步原语，是JMM在实际开发中的具体体现\n- [/编程语言/并发模型.md](/编程语言/并发模型.md) 高层并发抽象（Actor / CSP / 共享内存等）的并列视角，与 JMM 的共享内存语义互为对照\n- [/计算机系统/程序结构和执行/存储器层次结构.md](/计算机系统/程序结构和执行/存储器层次结构.md) JMM 访存期的硬件地基：多级缓存 / MESI / store buffer 是可见性问题的物理来源\n- [/计算机系统/程序结构和执行/处理器体系架构.md](/计算机系统/程序结构和执行/处理器体系架构.md) JMM 执行期要驯服的现象源：乱序执行 / 推测执行\n- [/操作系统/多处理机系统.md](/操作系统/多处理机系统.md) JMM 跨核可见性的硬件根：缓存一致性协议\n","metadata":"tags: ['编程语言', '并发编程', '性能']","hasMoreCommit":false,"totalCommits":2,"commitList":[{"date":"2026-07-21T15:39:57+08:00","author":"MY","message":"docs(JVM): 完善Java内存模型文档并更新相关链接","hash":"f2ccd6d5addc15acf1e4a44dfbf6dab1ebe1b110"},{"date":"2026-06-29T19:45:02+08:00","author":"MY","message":"docs(overview): 更新知识库概览文档的结构分析和质量评估","hash":"7998a20f117673f0493a5070c9c1f66887495222"}],"createTime":"2026-06-29T19:45:02+08:00"}