关于java:全网最硬核-Java-新内存模型解析与实验-5-JVM-底层内存屏障源码分析

17次阅读

共计 4011 个字符,预计需要花费 11 分钟才能阅读完成。

集体创作公约:自己申明创作的所有文章皆为本人原创,如果有参考任何文章的中央,会标注进去,如果有疏漏,欢送大家批评。如果大家发现网上有剽窃本文章的,欢送举报,并且踊跃向这个 github 仓库 提交 issue,谢谢反对~

本篇文章参考了大量文章,文档以及论文,然而这块货色真的很繁冗,我的程度无限,可能了解的也不到位,如有异议欢送留言提出。本系列会不断更新,联合大家的问题以及这里的谬误和疏漏,欢送大家留言

如果你喜爱单篇版,请拜访:全网最硬核 Java 新内存模型解析与试验单篇版(不断更新 QA 中)
如果你喜爱这个拆分的版本,这里是目录:

  • 全网最硬核 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(地址:https://docs.oracle.com/javase/specs/jls/se17/html/jls-17.html),其中包含同步、Wait/Notify、Sleep & Yield 以及内存模型等等做了很多标准解说。然而也置信大多数人和我一样,第一次读的时候,感觉就是在看热闹,看完了只是晓得他是这么规定的,然而为啥要这么规定,不这么规定会怎么样,并没有很清晰的意识。同时,联合 Hotspot 的实现,以及针对 Hotspot 的源码的解读,咱们甚至还会发现,因为 javac 的动态代码编译优化以及 C1、C2 的 JIT 编译优化,导致最初代码的体现与咱们的从标准上了解出代码可能的体现是不太统一的。并且,这种不统一,导致咱们在学习 Java 内存模型(JMM,Java Memory Model),了解 Java 内存模型设计的时候,如果想通过理论的代码去试,后果是与本人原本可能正确的了解被带偏了,导致误会。
我自己也是一直地尝试了解 Java 内存模型,重读 JLS 以及各路大神的剖析。这个系列,会梳理我集体在浏览这些标准以及剖析还有通过 jcstress 做的一些试验而得出的一些了解,心愿对于大家对 Java 9 之后的 Java 内存模型以及 API 形象的了解有所帮忙。然而,还是强调一点,内存模型的设计,出发点是让大家能够不必关怀底层而形象进去的一些设计,波及的货色很多,我的程度无限,可能了解的也不到位,我会尽量把每一个论点的论据以及参考都摆出来,请大家不要齐全置信这里的所有观点,如果有任何异议欢送带着具体的实例反驳并留言

8. 底层 JVM 实现剖析

8.1. JVM 中的 OrderAccess 定义

JVM 中有各种用到内存屏障的中央:

  1. 实现 Java 的各种语法元素(volatile,final,synchronized,等等)
  2. 实现 JDK 的各种 API(VarHandle,Unsafe,Thread,等等)
  3. GC 须要的内存屏障:因为要思考 GC 多线程与利用线程(在 GC 算法中叫做 Mutator)的工作形式,到底是进行世界(Stop-the-world,STW)的形式,还是并发的形式

    1. 对象援用屏障:例如分代 GC,复制算法,年老代 GC 的时候咱们个别是从一个 S 区复制存活对象到另一个 S 区,如果 复制的过程,咱们不想进行世界(Stop-the-world,STW),而是和利用线程同时进行,那么咱们就须要内存屏障,例如;
    2. 保护屏障:例如分区 GC 算法,咱们须要保护每个区的跨区援用表以及应用情况表,例如 Card Table。这个如果咱们想要利用线程与 GC 线程并发批改拜访,而不是进行世界,那么也须要内存屏障
  4. JIT 也须要内存屏障:同样地,利用线程到底是解释执行代码还是执行 JIT 优化后的代码,这里也是须要内存屏障的。

这些内存屏障,不同的 CPU,不同的操作系统,底层须要不同的代码实现,对立的接口设计是:

源代码地址:orderAccess.hpp

不同的 CPU,不同的操作系统实现是不一样的,联合后面 CPU 乱序表格:

咱们来看下 linux + x86 的实现:

源代码地址:orderAccess_linux_x86.hpp

对于 x86,因为 Load 与 Load,Load 与 Store,Store 与 Store 原本有一致性保障,所以只有没有编译器乱序,那么就天生有 StoreStore,LoadLoad,LoadStore 屏障,所以这里咱们看到 StoreStore,LoadLoad,LoadStore 屏障的实现都只是加了编译器屏障。同时,前文中咱们剖析过,acquire 其实就是相当于在 Load 前面加上 LoadLoad,LoadStore 屏障,对于 x86 还是须要编译器屏障就够了。release 咱们前文中也剖析过,其实相当于在 Store 后面加上 LoadStore 和 StoreStore,对于 x86 还是须要编译器屏障就够了。于是,咱们有如下表格:

咱们再看下后面咱们常常应用的 Linux aarch64 下的实现:

源代码地址:orderAccess_linux_aarch64.hpp

如后面表格外面说,ARM 的 CPU Load 与 Load,Load 与 Store,Store 与 Store,Store 与 Load 都会乱序。JVM 针对 aarch64 没有间接应用 CPU 指令,而是应用了 C++ 封装好的内存屏障实现。C++ 封装好的很像咱们后面讲的繁难 CPU 模型的内存屏障,即读内存屏障(__atomic_thread_fence(__ATOMIC_ACQUIRE)),写内存屏障(__atomic_thread_fence(__ATOMIC_RELEASE)),读写内存屏障(全内存屏障,__sync_synchronize())。acquire 的作用是作为接收点解包让前面的都看到包外面的内容,类比繁难 CPU 模型,其实就是阻塞期待 invalidate queue 齐全解决完保障 CPU 缓存没有脏数据。release 的作用是作为发射点将后面的更新打包收回去,类比繁难 CPU 模型,其实就是阻塞期待 store buffer 齐全刷入 CPU 缓存。所以,acquire,release 别离应用读内存屏障和写内存屏障实现。

LoadLoad 保障第一个 Load 先于第二个,那么其实就是在第一个 Load 前面退出读内存屏障,阻塞期待 invalidate queue 齐全解决完;LoadStore 同理,保障第一个 Load 先于第二个 Store,只有 invalidate queue 解决完,那么以后 CPU 中就没有对应的脏数据了,就不须要期待以后的 CPU 的 store buffer 也清空。

StoreStore 保障第一个 Store 先于第二个,那么其实就是在第一个写入前面放读内存屏障,阻塞期待 store buffer 齐全刷入 CPU 缓存;对于 StoreLoad,比拟非凡,因为第二个 Load 须要看到 Store 的最新值,也就是更新不能只到 store buffer,同时过期不能存在于 invalidate queue 未解决,所以须要读写内存屏障,即全屏障。

8.2. volatile 与 final 的内存屏障源码

咱们接下来看一下 volatile 的内存屏障插入的相干代码,以 arm 为例子. 咱们其实通过跟踪 iload 这个字节码就可以看进去如果 load 的是 volatile 关键字或者 final 关键字润饰的字段会怎么样,以及 istore 就可以看进去如果 store 的是 volatile 关键字或者 final 关键字润饰的字段会怎么样

对于字段拜访,JVM 中也有疾速门路和慢速门路,咱们这里只看疾速门路的代码:

对应源码:

源代码地址:templateTable_arm.cpp

微信搜寻“我的编程喵”关注公众号,加作者微信,每日一刷,轻松晋升技术,斩获各种 offer

我会常常发一些很好的各种框架的官网社区的新闻视频材料并加上集体翻译字幕到如下地址(也包含下面的公众号),欢送关注:

  • 知乎:https://www.zhihu.com/people/…
  • B 站:https://space.bilibili.com/31…
正文完
 0