个人创作公约:本人声明创作的所有文章皆为自己原创,如果有参考任何文章的地方,会标注出来,如果有疏漏,欢迎大家批判。如果大家发现网上有抄袭本文章的,欢迎举报,并且积极向这个 github 仓库 提交 issue,谢谢支持~
本篇文章参考了大量文章,文档以及论文,但是这块东西真的很繁杂,我的水平有限,可能理解的也不到位,如有异议欢迎留言提出。本系列会不断更新,结合大家的问题以及这里的错误和疏漏
- 全网最硬核 Java 新内存模型解析与实验 - 1. 什么是 Java 内存模型
- 全网最硬核 Java 新内存模型解析与实验 - 2. 原子访问与字分裂
- 全网最硬核 Java 新内存模型解析与实验 - 3. 硬核理解内存屏障(CPU 编译器)
- 全网最硬核 Java 新内存模型解析与实验 - 4. Java 新内存访问方式与实验
- 全网最硬核 Java 新内存模型解析与实验 - 5. JVM 底层内存屏障源码分析
JMM 相关文档:
- Java Language Specification Chapter 17
- The JSR-133 Cookbook for Compiler Writers - Doug Lea’s
- Using JDK 9 Memory Order Modes - Doug Lea’s
内存屏障,CPU 与内存模型相关:
- Weak vs. Strong Memory Models
- Memory Barriers: a Hardware View for Software Hackers
- A Detailed Analysis of Contemporary ARM and x86 Architectures
- Memory Model = Instruction Reordering Store Atomicity
- Out-of-Order Execution
x86 CPU 相关资料:
- x86 wiki
- Intel® 64 and IA-32 Architectures Software Developer Manuals
- Formal Specification of the x86 Instruction Set Architecture
ARM CPU 相关资料:
- ARM wiki
- aarch64 Cortex-A710 Specification
各种一致性的理解:
- Coherence and Consistency
Aleskey 大神的 JMM 讲解:
- Aleksey Shipilëv - 不要误解Java内存模型(上)
- Aleksey Shipilëv - 不要误解Java内存模型(下)
相信很多 Java 开发,都使用了 Java 的各种并发同步机制,例如 volatile,synchronized 以及 Lock 等等。也有很多人读过 JSR 第十七章 Threads and Locks,其中包括同步、Wait/Notify、Sleep & Yield 以及内存模型等等做了很多规范讲解。但是也相信大多数人和我一样,第一次读的时候,感觉就是在看热闹,看完了只是知道他是这么规定的,但是为啥要这么规定,不这么规定会怎么样,并没有很清晰的认识。同时,结合 Hotspot 的实现,以及针对 Hotspot 的源码的解读,我们甚至还会发现,由于 javac 的静态代码编译优化以及 C1、C2 的 JIT 编译优化,导致最后代码的表现与我们的从规范上理解出代码可能的表现是不太一致的。并且,这种不一致,导致我们在学习 Java 内存模型(JMM,Java Memory Model),理解 Java 内存模型设计的时候,如果想通过实际的代码去试,结果是与自己本来可能正确的理解被带偏了,导致误解。 我本人也是不断地尝试理解 Java 内存模型,重读 JLS 以及各路大神的分析。这个系列,会梳理我个人在阅读这些规范以及分析还有通过 jcstress 做的一些实验而得出的一些理解,希望对于大家对 Java 9 之后的 Java 内存模型以及 API 抽象的理解有所帮助。但是,还是强调一点,内存模型的设计,出发点是让大家可以不用关心底层而抽象出来的一些设计,涉及的东西很多,我的水平有限,可能理解的也不到位,我会尽量把每一个论点的论据以及参考都摆出来,请大家不要完全相信这里的所有观点,如果有任何异议欢迎带着具体的实例反驳并留言。
1. 理解“规范”与“实现”
首先,我想先参考 Aleksey Shipilëv 大神的理解思路,即首先分清楚规范(Specification)与实现(Implementation)的区别。前面提到的 JLS(Java Language Specification)其实就是一种规范,它规范了 Java 语言,并且所有能编译运行 Java 语言的 JDK 实现都要实现它里面规定的功能。但是对于实际的实现,例如 Hotspot JVM 的 JDK,就是具体的实现了,从规范到实际的实现,其实是有一定的差异的。首先是下面这个代码:
实际 HotSpot 最后编译并且经过 JIT 优化与 CPU 指令优化运行的代码其实是:
即将结果 3 放入寄存器并返回,这样与原始代码其实效果是一致的,省略了无用的本地变量操作,也是合理的。那么你可能会有疑问:不会呀,我打断点运行到这里的时候,能看到本地变量 x,y,result 呀。这个其实是 JVM 运行时做的工作,如果你是以 DEBUG 模式运行 JVM,那么其实 JIT 默认就不会启用,只会简单的解释执行,所以你能看到本地变量。但是实际执行中,如果这个方法是热点方法,经过 JIT 的优化,这些本地变量其实就不存在了。
还有一个例子是,Hotspot 会有锁膨胀机制(这个我们后面还会测试),即:
如果按照 JLS 的描述,那么 x = 1 与 y = 1 这两个操作是不能重排序的。但是 Hotspot 实际的实现会将上面的代码优化成:
那么这样,其实 x = 1 与 y = 1 这两个操作就可以重排序了,这个我们后面也会验证。
不同的 JVM 实现,实际的表现都会有些差异。并且就算是同一个 JVM 实现,在不同的操作系统,硬件环境等等,表现也有可能不一样。例如下面这个例子:
正常情况下,r1 的值应该只有 {-1, 0}
这两个结果之一。但是在某些 32 位的 JVM 上执行会有些问题,例如在 x86_32 的环境下,可能会有 {-1, 0, -4294967296, 4294967295}
这些结果。
所以,如果我们要全面的覆盖底层到 JMM 设计以及 Hotspot 实现和 JIT 优化等等等等,涉及的东西太多太多,一层逻辑套逻辑,面面俱到我真的做不到。并且我也没法保证我理解的百分百准确。如果我们要涉及太多的 HotSpot 实现,那么我们可能就偏离了我们这个系列的主题,我们其实主要关心的是 Java 本身内存模型的设计规范,然后从中总结出我们在实际使用中,需要知道并且注意的点的最小集合,这个也是本系列要梳理的,同时,为了保证本系列梳理出的这个最小集合准确,会加上很多实际测试的代码,大家也可以跑一下看看这里给出的结论以及对于 JMM 的理解是否正确。