1. 概述

在执行程序时, 为了提高性能, 编译器和处理器常常会对指令做重排序. 为了实现某些功能有时会禁止某些重排序, 由此引入了内存屏障.

2. 重排序

重排序虽然可以提高程序性能, 但是编译器和处理器不会改变存在数据依赖关系的两个操作的执行顺序. 即: 编译器和处理器在重排序时, 会遵
守数据依赖性.

这里说的数据依赖性仅针对单个处理器中执行的指令序列和单个线程中执行的操作, 不同处理器之间和不同线程之间的数据依赖性不被编译器和处理器考虑.

2-1. as-if-serial语义

as-if-serial语义的意思是: 不管怎么重排序(编译器和处理器为了提高并行度), (单线程)程序的执行结果不能被改变. 编译器、runtime和处理器都必须遵守as-if-serial语义.

为了遵守as-if-serial语义, 编译器和处理器不会对存在数据依赖关系的操作做重排序, 因为这种重排序会改变执行结果. 但是, 如果操作之间不存在数据依赖关系, 这些操作就可能被编译器和处理器重排序.

2-2. 重排序的种类

  1. 编译器优化的重排序: 编译器在不改变单线程程序语义的前提下, 可以重新安排语句的执行顺序.
  2. 指令级并行的重排序: 现代处理器采用了指令级并行技术(Instruction-Level Parallelism, ILP)来将多条指令重叠执行. 如果不存在数据依赖性, 处理器可以改变语句对应机器指令的执行顺序.
  3. 内存系统的重排序: 由于处理器使用缓存和读/写缓冲区, 这使得加载和存储操作看上去可能是在乱序执行.

2-3. 从Java源代码到最终实际执行的指令序列, 会分别经历下面3中重排序.

源代码 -> 1:编译器优化重排序 -> 2:指令级并行重排序 -> 3:内存系统重排序 -> 最终执行的指令序列

其中1属于编译器重排序, 2和3属于处理器重排序. 这些重排序可能会导致多线程程序出现内存可见性问题. 对于编译器, JMM编译器重排序规则会禁止特性类型的编译器重排序(并不是所有的编译器重排序都要禁止); 对于处理器重排序, JMM的处理器重排序规则会要求Java编译器在生成指令序列时, 插入特性类型的内存屏障(Memory Barriers, Intel称之为Memory Fence)指令, 通过内存屏障指令来禁止特定类型的处理器重排序.

JMM属于语言级的内存模型, 它确保在不同的编译器和不同的处理器平台之上, 通过禁止特性类型的编译器重排序和处理器重排序, 为程序员提供一致的内存可见性保证.

3. 内存屏障类型

现代的CPU使用写缓冲区临时保存向内存写入的数据. 写缓冲区可以保证指令流水线持续运行, 它可以避免由于处理器停顿下来等待向内存写入数据而产生的延迟. 同时, 通过以批处理的方式刷新写缓冲区, 以及合并写缓冲区中对同一内存地址的多次写, 减少对内存总线的占用. 虽然写缓冲区有这么多好处, 但是每个处理器的写缓冲区仅仅对它所在的处理器可见. 这个特性会对内存操作的执行顺序产生重要的影响: 处理器对内存的读/写操作的执行顺序. 不一定与内存实际发生的读写操作顺序一致.

写缓冲区仅对自己的处理器可见, 它会导致处理器执行内存操作的顺序可能会与内存实际的操作执行顺序不一致. 由于处理器都会使用写缓冲区, 因此现代处理器都会允许对写-读操作进行重排序.

3-1. 处理器的重排序规则

可以发现常见的处理器都允许StoreLoad重排序; 常见的处理器都不允许对存在数据依赖的操作做重排序. SPARC-TSO和X86拥有相对较强的处理器内存模型, 它们仅允许对写-读操作做重排序(因为它们都使用了写缓冲区).

3-2. 内存屏障类型表

为了保证内存可见性, Java编译器在生成指令序列的适当位置会插入内存屏障指令来禁止特定类型的处理器重排序.

StoreLoad Barriers是一个"全能型"的屏障, 它同时具有其他3个屏障的效果. 现代的多处理器大多支持该屏障(其他类型的屏障不一定被所有处理器支持). 执行该屏障开销会很昂贵, 因为当前处理器通常要把写缓冲区中的数据全部刷新到内存中(Buffer Fully Flush).

4. 总结

重排序可以提高性能, 但是重排序可能会导致内存可见性问题, 问了解决这个问题, 编译器在生成字节码的时候会插入特定类型的内存屏障来禁止重排序, 保证多线程下的内存可见性.

JMM中的重排序及内存屏障的更多相关文章

  1. J.U.C JMM. pipeline.指令重排序,happen-before(续)

    前面已经介绍硬件平台Cache Coherence问题和解决办法,下面来看看Java虚拟机平台的相关知识.硬件平台处理器,高速缓存,主存之间的交互关系如下: Java内存模型(JMM)         ...

  2. J.U.C JMM. pipeline.指令重排序,happen-before

    pipeline: 现在的CPU一般采用流水线方式来执行指令.一个指令执行周期被分成:取值,译码,执行,访存,写会,更新PC若干阶段.然后,多条指令可以同时存在于流水线中,同时被执行,来提高系统的吞吐 ...

  3. Jvm 中的 重排序、主存、原子操作

    一.重排序 好处:重排序可以提升性能,避免在一个耗时很长的指令在“执行”阶段呆很长时间,而导致后续的指令都卡在“执行”之前的阶段上. 坏处:重排序对多线程的影响 class ReorderExampl ...

  4. JS中数组重排序方法

    在数组中有两个可以用来直接排序的方法,分别是reverse()和sort().下面通过本文给大家详细介绍,对js数组重排序相关知识感兴趣的朋友一起看看吧 1.数组中已存在两个可直接用来重排序的方法:r ...

  5. Javascript中数组重排序方法详解

    在数组中有两个可以用来直接排序的方法,分别是reverse()和sort().下面通过本文给大家详细介绍,对js 数组重排序相关知识感兴趣的朋友一起看看吧. 1.数组中已存在两个可直接用来重排序的方法 ...

  6. J.U.C JMM. pipeline.指令重排序,happen-before(续MESI协议)

    缓存(Cache)       CPU的读/写(以及取指令)单元正常情况下甚至都不能直接访问内存——这是物理结构决定的:CPU都没有管脚直接连到内存.相反,CPU和一级缓存(L1 Cache)通讯,而 ...

  7. Java内存访问重排序笔记

    >>关于重排序 重排序通常是编译器或运行时环境为了优化程序性能而采取的对指令进行重新排序执行的一种手段. 重排序分为两类:编译期重排序和运行期重排序,分别对应编译时和运行时环境. > ...

  8. JVM内存模型、指令重排、内存屏障概念解析

    在高并发模型中,无是面对物理机SMP系统模型,还是面对像JVM的虚拟机多线程并发内存模型,指令重排(编译器.运行时)和内存屏障都是非常重要的概念,因此,搞清楚这些概念和原理很重要.否则,你很难搞清楚哪 ...

  9. JVM内存模型、指令重排、内存屏障概念解析(转载)

    在高并发模型中,无是面对物理机SMP系统模型,还是面对像JVM的虚拟机多线程并发内存模型,指令重排(编译器.运行时)和内存屏障都是非常重要的概念,因此,搞清楚这些概念和原理很重要.否则,你很难搞清楚哪 ...

随机推荐

  1. ARP【地址解析协议】理解

    今天是来公司的第二个周一,早上收到Boss抄送的邮件说网段之间无法通信,心想现在还不太懂这个原理,于是就在网络上搜罗了一下资料,作此整理(大部分文字内容来自网络) 1. 同网段和不同网段设备通信原理详 ...

  2. winform获取EXE图片

    winform获取EXE图片 using (FileStream fs = new System.IO.FileStream(n, FileMode.OpenOrCreate, FileAccess. ...

  3. git diff 与git format-patch 生成补丁包

    git diff commit_id 会生成最后一次提交到目前修改过的内容补丁 git diff commit_id1 commit_id2 会生成两次提交之间修改过的内容补丁 git format- ...

  4. node.js 调用第三方服务

    node作为客户端调用第三方服务   nodejs.cn/api 1. let http = require('http'); let util = require("util") ...

  5. MAC如何生成SSH key与GitHub关联

    一.检查 SSH key 是否存在 在终端输入如下代码: ls -al ~/.ssh 如果没有,终端显示如下: No such file or directory 如果有,终端显示如下: ➜ ~ ls ...

  6. SQL-触发器-011

    什么事触发器? 触发器是一种特殊的存储过程,当表中的数据发生改变时触发器自动生效: 触发器无法通过名称调用,也不允许设置参数. 触发器的类型: DML触发器(数据操作语言触发器-insert/upda ...

  7. I think I need a boat house

    I think I need a boat house. Fred Mapper is considering purchasing some land in Louisiana to build h ...

  8. 模拟器运行android镜像

    编译完成后需要运行一下,看看效果,操作中出现很多问题   先说明android4.0的,然后说明一下android2.3.4的,两者是一样的   一.android4.0.1 可以参看如下链接: ht ...

  9. Scrapy学习篇(十一)之设置随机User-Agent

    大多数情况下,网站都会根据我们的请求头信息来区分你是不是一个爬虫程序,如果一旦识别出这是一个爬虫程序,很容易就会拒绝我们的请求,因此我们需要给我们的爬虫手动添加请求头信息,来模拟浏览器的行为,但是当我 ...

  10. ubuntu crontab python 定时任务备记

    crontab -e 写入: # at a.m every week with: # * * tar -zcf /var/backups/home.tgz /home/ # # For more in ...