news 2026/10/13 6:54:42

JMM:Java 内存模型学习笔记

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JMM:Java 内存模型学习笔记
概念层级共享区域 (Shared)线程私有/工作区 (Working)
JMM (抽象规范)主存 (Main Memory)

(存实例字段, 静态字段, 数组) |本地内存 (Working Memory)

(存共享变量的副本) |
|JVM (具体实现)|Java 堆 (Java Heap)

(存对象实例, 数组) |(无对应区域)“工作内存” 并不指 JVM 栈帧里的局部变量表

JMM 本地内存不是 JVM 栈|
|硬件 (物理真实)|物理内存 (RAM)|CPU 高速缓存 (L1/L2/L3)

CPU 寄存器 (Registers)|

JMM 内存模型

区别于JVM内存结构 可以看作是 Java 定义的并发编程相关的一组规范

主要定义了对于一个共享变量,当另一个线程对这个共享变量执行写操作后,这个线程对这个共享变量的可见性

原子性

在 Java 中,可以借助synchronized、各种Lock以及各种原子类实现原子性。

synchronized和各种Lock可以保证任一时刻只有一个线程访问该代码块,因此可以保障原子性。各种原子类是利用 CAS (compare and swap) 操作(可能也会用到volatile或者final关键字)来保证原子操作。

可见性

由JVM缓存优化引起的

在 Java 中,可以借助synchronized、volatile以及各种Lock实现可见性。

譬如我们将变量声明为volatile,这就指示 JVM,这个变量是共享且不稳定的,每次使用它都到主存中进行读取。

有序性

由于CPU指令重排序优化,代码的执行顺序未必就是编写代码时候的顺序。

指令重排序可以保证串行语义一致,但是没有义务保证多线程间的语义也一致,所以在多线程下,指令重排序可能会导致一些问题。

volatile关键字可以禁止重排序优化

volatile关键字
  • 可见性:线程每次读取都会从主内存重新获取最新值,立刻把值从工作内存刷新到主内存;
  • 有序性:禁止某些指令重排序优化,确保读写顺序在多线程间一致。
  • 单独不能保证原子性
  • 故适用于一写多读
原理 内存屏障

对 volatile 变量读操作后面加上屏障,防止之后的代码跑前面、对 volatile 变量写操作前面加上屏障,防止之前的代码跑后面

DCL 线程问题
  1. 为uniqueInstance分配内存空间

  2. 初始化uniqueInstance

  3. 将 uniqueInstance 指向分配的内存地址 但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2

    因为uniqueInstance没有被synchronized“完全包含”,故不能保证有序性

解决方法:uniqueInstance加上 volatile即可

happens-before原则

可见性和有序性的规则,描述哪些操作对其他操作可见、哪些操作必须在其他操作之前执行

简单来说就是描述两个操作之间的内存可见性

概念含义
happens-before定义多线程操作的“逻辑先后关系”,保证可见性与有序性
目的限制编译器与 CPU 的指令重排,确保程序语义正确
实现方式语言内存模型(如 JMM)+ 内存屏障(Memory Barrier)
本质一种抽象的逻辑时间顺序,不等同于物理执行顺序

一共8条

  1. 程序顺序规则:一个线程内,按照代码顺序,书写在前面的操作 happens-before 于书写在后面的操作;
  2. 解锁规则:解锁 happens-before 于加锁;
  3. volatile 变量规则:对一个 volatile 变量的写操作 happens-before 于后面对这个 volatile 变量的读操作。说白了就是对 volatile 变量的写操作的结果对于发生于其后的任何操作都是可见的。
  4. 传递规则:如果 A happens-before B,且 B happens-before C,那么 A happens-before C;
  5. 线程启动规则:Thread 对象的start()方法 happens-before 于此线程的每一个动作。

如果两个操作不满足上述任意一个 happens-before 规则,那么这两个操作就没有顺序的保障,JVM 可以对这两个操作进行重排序。

synchronized优化

完整的锁升级路径是:无锁 -> (偏向锁 )-> 轻量级锁 -> 重量级锁

轻量级加锁

锁记录 (Lock Record) 存在于线程的栈帧上,在轻量级锁“加锁”时用于备份对象头的 Mark Word,在“解锁”时用于恢复 Mark Word。这个“备份”和“恢复”的动作都是通过 CAS 原子操作完成的

当线程 A 准备进入一个synchronized(obj)代码块时:

  1. 创建锁记录:JVM 在线程 A 的当前栈帧中创建一个 Lock Record。
  2. 复制 Mark Word:JVM 将obj对象头中的 Mark Word 复制到这个 Lock Record 的displaced_header字段中。
  3. CAS 尝试替换:JVM 使用CAS原子操作,将obj对象头中的 Mark Word 替换为一个指向这个 Lock Record 的指针,最后两位锁标志位从“01”变为 “00”(表示轻量级锁状态)。
    • CAS 成功:
      • 线程 A 成功获得轻量级锁,可以执行同步代码块。
    • CAS 失败:1锁竞争:这说明在线程 A 尝试 CAS 的瞬间,有其他线程已经抢先一步持有了obj的轻量级锁.锁膨胀为重量级锁。 2锁重入:新增锁记录 但内容为null
  4. 解锁 先消除null值锁记录 cas还原锁对象的Mark Word
锁膨胀

轻量锁发生锁竞争 则锁对象申请monitor 把锁对象的markword置为指向monitor的指针

锁对象 的 Mark Word 会被修改为指向一个重量级锁的 Monitor 对象,而竞争失败的线程会进入阻塞状态。 当持有锁的线程释放时候就明白了锁已经膨胀了,于是它会按照重量级锁的释放流程

monitorenter是synchronized代码块的唯一入口指令。JVM 在执行这条指令时,会“智能地”决定在内部是使用 CAS(轻量级锁)还是使用 Mutex(重量级锁),而Lock Record只是在 CAS 过程中用到的一种栈上数据结构

重量级锁优化

因为阻塞需要线程上下文切换 导致巨大的性能开销

自旋优化(单核则无意义):就是不直接阻塞,先重试几次/busy-wating循环

弊端:失败了总开销比直接阻塞还要大 自适应自旋,JVM动态决定重试次数

偏向锁优化

偏向锁在JDK 15后默认关闭(Deprecated)

只有第一次CAS将操作系统的线程ID设置到锁对象的mark word头(而非锁记录),将对象头中是否偏向锁标志位置为1,后续当前线程再次访问这个共享资源时,会根据偏向锁标识跟线程ID进行比对是否相同,比对成功则直接获取到锁

弱点:昂贵的“撤销”成本

偏向锁的乐观假设一旦被打破,就会付出沉重代价

  • 竞争出现:当另一个线程(线程 B)尝试获取这个“已偏向线程 A”的锁时,竞争就出现了。
  • 触发撤销(Revocation):此时,偏向锁必须被撤销。这个过程非常昂贵:
    1. JVM 必须等待一个全局安全点 (Safepoint),这会暂停所有用户线程(Stop-The-World, STW)。
    2. JVM 检查持有锁的线程 A 是否还在同步块中。
    3. 如果 A 还在:锁不能撤销,必须“膨胀”为轻量级锁。
    4. 如果 A 已退出:锁恢复为“可偏向”或“无锁”状态,以便线程 B 尝试获取。
  • 性能反噬:如果一个锁在不同线程间频繁切换(即存在竞争),那么偏向锁的获取和撤销开销(尤其是 Safepoint)会导致性能急剧下降,远不如一开始就使用轻量级锁。

特性:

  • 批量重偏向:如果发现某一“类”的对象(不是单个对象)上的偏向锁被频繁撤销,JVM 会认为这个类的锁不再适合偏向“第一个线程”。它会等到下一次安全点,将这个类所有实例的锁批量地重偏向给“当前正在竞争的线程”。 批量重偏向不改变任何已存在对象的状态,它只是“递增Klass的epoch”,使得所有旧的偏向在下一次访问时“失效”,从而允许新的线程直接对它们进行重新偏向
  • 批量撤销:如果批量重偏向后,竞争依然非常激烈,JVM 会最终放弃,将这个类的所有实例的偏向锁功能批量地撤销掉,使其新建的实例默认就是“不可偏向”的(直接进入轻量级锁)。
锁消除

JVM 虚拟机如果检测不到某段代码被共享和竞争的可能性,将同步锁消除掉


原文地址:JMM

本文持续更新,最新内容及相关资料请访问原文。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/13 6:53:32

鼻内镜检查为何喷药?麻药原理与操作细节全解

做鼻内镜检查的人,第一次听说“还要在鼻子里喷药”,大多数人第一反应是松一口气:至少有个准备动作,少遭点罪。可真到了诊室,麻药喷进去那一刻,不少人的表情反而是懵的——这药不是想象中的“清凉一下”&…

作者头像 李华
网站建设 2026/10/13 6:53:10

软考 系统架构设计师历年真题集萃(46)

接前一篇文章:软考 系统架构设计师系列知识点之杂项集萃(45) 第73题 支持电子邮件加密服务的标准或技术是( )。 A. PGP B. PKI C. SET D. Kerberos 正确答案:A。 所属知识点:旧版教材 信息安全 -> 网络安全协议。 试题解析: PGP PGP是一个基于RSA的邮件加密软…

作者头像 李华
网站建设 2026/10/13 6:50:51

AI聊天机器人三大安全攻击事件拆解:信任滥用、指令注入与供应链投毒

1. 当聊天窗口变成攻击入口你可能已经习惯了每天跟AI聊天机器人打交道——写邮件、查资料、生成代码、做表格,甚至让它帮你分析合同条款。但你想过没有,这个你越来越依赖的对话窗口,本身就可能是一个精心设计的攻击入口?我做了十多…

作者头像 李华
网站建设 2026/10/13 6:50:23

LangChain4j RAG重排序实战:从向量召回到Cross-Encoder精排

前阵子维护一个基于 LangChain4j 的内部知识库问答机器人,遇到的第一个尴尬不是模型不会说,而是它“引错书”。用户问“数据库连接池的 maxLifetime 设置不合理会有哪些隐患”,系统从 500 多篇技术文档里找回 4 篇,结果第一篇讲的…

作者头像 李华
网站建设 2026/10/13 6:50:02

Fortran语法介绍(一):用TaoToken统一Key跑通第一个数值计算示例

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华