我对Java内存模型的理解
编者注:本文为历史博文归档;涉及 JDK、框架与工具链版本请以当前官方文档为准。引用外链图片可能失效,阅读时请注意时效性。
引言
在所有编程语言中,「内存模型」(Memory Model)都是一个核心概念。区别于微架构层面的内存模型,高级语言的内存模型通常涵盖了编译器(Compiler)与微架构(Microarchitecture)两部分。笔者曾研究过 Java、C# 和 Go 语言的内存模型,发现其核心思想大同小异,主要差异在于具体实现细节。
本文将重点探讨 Java 内存模型(Java Memory Model, JMM)。
Java 内存模型抽象图
提到 Java 内存模型,开发者通常会对下图非常熟悉:
这张图揭示了线程运行时的内存交互机制:每个线程拥有一块专用的「工作内存」(Work Memory)。Java 程序将变量同步到线程所在的工作内存后,线程操作的是工作内存中的变量副本。至于工作内存中的变量值何时同步回「主内存」(Main Memory),则是不可预期的。
为了保证线程安全,Java 内存模型规定通过使用关键词 synchronized 或 volatile 来满足特定的约束:
volatile:保证读写操作直接针对主内存中的变量。synchronized:保证在代码块开始时将主内存的值同步到工作内存,而在块结束时将变量同步回主内存。
基于以上描述,理论上我们可以写出线程安全的 Java 程序,JDK 也在很大程度上屏蔽了底层细节。
工作内存的真实含义
然而,当深入探究 JVM 实现时,你会发现实际上并不存在物理意义上的「工作内存」,即内存中并不会专门分配一块空间来运行 Java 程序。那么,「工作内存」究竟指代什么?
这个问题曾困扰笔者许久,因为在 JVM 实现中很难找到与主内存同步的直接代码。例如,当使用 volatile 时,在源代码层面往往只能看到类似以下的汇编指令调用:
__asm__ volatile ("lock; addl $0,0(%%esp)" : : : "cc", "memory");这条指令在部分微架构上的主要功能是防止指令重排(Instruction Reordering),即确保该指令前后的其它指令不会越过这个界限执行[注 1]。
硬件层面的内存一致性
在现代 x86/x64 微架构中,读写内存的一致性主要通过 MESI 协议(Intel 使用 MESI-F,AMD 使用 MOESI)来保证[注 2]。MESI 的状态转换图如下:
更详细的中文文档描述可参考:http://blog.csdn.net/zhuliting/article/details/6210921
编译器与微架构的优化
既然物理上不存在「工作内存」,那么 Java 内存模型中所说的概念究竟映射到哪里?笔者的理解是,「工作内存」是一个虚拟概念,其承载主体主要包含两部分:
- 编译器
- 微架构
编译器优化
编译器的目标通常是执行速度越快越好。因此,编译器会尽量减少从内存读取数据的次数。如果一个数据已被加载到寄存器中,直接使用寄存器中的值性能最高。但这可能导致线程读不到最新的值。
通过在 Java 语言中为变量加上 volatile 修饰符,可以强制告诉编译器该变量必须从主内存获得,此时编译器将不会进行此类缓存优化【案例可参考参考资料 5(一个 .Net 示例)】。
微架构与指令重排
对于微架构而言,在 x86/x64 环境下,CPU 在执行指令时可能会进行指令重排,即编译器生成的指令顺序与 CPU 实际执行的顺序可能不一致。当使用变量作为信号标志时,这种重排可能导致严重问题。例如以下代码:
x = 0;
y = 0;
i = 0;
j = 0;
// thread A
y = 1;
x = 1;
// thread B
i = x;
j = y;上述代码执行后,i 和 j 的值会是多少?答案是:00、01、10、11 均有可能。
若希望得到确定的结果,则需要通过 synchronized(或者 java.util.concurrent.locks)来进行线程间同步。
总结
综上所述,笔者对 Java 内存模型的理解如下:
在编译器各种优化及多种类型的微架构平台上,Java 语言规范制定者试图创建一个虚拟的概念(即 JMM),并传递给 Java 程序员,使他们能够基于这个虚拟概念写出线程安全的程序。而编译器实现者会根据 Java 语言规范中的各种约束,在不同的平台上达成程序员所需的线程安全目标。
说明与参考资料
版本说明:本文基于 Java 5(JSR-133)及之后的内存模型规范撰写。现代 JVM 实现细节可能随版本迭代而变化,请以官方文档为准。文中汇编示例涉及 32 位寄存器(%%esp),在 64 位环境下可能有所不同。注释与参考文献:
- [注 1] 关于
lock前缀的详细说明,可查看文档《Intel® 64 and IA-32 Architectures Software Developer's Manual, Volume 3A: System Programming Guide, Part 1》的 "CHAPTER 8 MULTIPLE-PROCESSOR MANAGEMENT" 章节。 - [注 2] 不同的微架构内存模型存在差异,本文主要指 x86/x64 架构。想了解其他微架构的处理方式,可参考:http://gee.cs.oswego.edu/dl/jmm/cookbook.html
扩展资料:
- 《Intel® 64 and IA-32 Architectures Software Developer's Manual Volume 3A: System Programming Guide, Part 1》
- http://en.wikipedia.org/wiki/MESI_protocol
- http://www.javaol.net/2010/10/java-memory-model/
- http://gee.cs.oswego.edu/dl/jmm/cookbook.html
- http://igoro.com/archive/volatile-keyword-in-c-memory-model-explained/
版权声明:本文为原创文章,版权归 戴老师的博客 所有,转载请联系博主获得授权。
本文地址:https://1diff.fun/archives/wo-dui-java-nei-cun-mo-xing-de-li-jie.html
如果对本文有什么问题或疑问都可以在评论区留言,我看到后会尽量解答。

