Java的Volatile的特征是任何读都能读到最新值,本质上是JVM通过内存屏障来实现的,让我们看看从字节码以及汇编码的角度,来看下是否真是如此?

一 Volatile与内存屏障

为了实现volatile内存语义,JMM会分别限制重排序类型。下面是JMM针对编译器制定的volatile重排序规则表:

举例来说,第三行最后一个单元格的意思是:在程序顺序中,当第一个操作为普通变量的读或写时,如果第二个操作为volatile写,则编译器不能重排序这两个操作。

从上表我们可以看出:

当第二个操作是volatile写时,不管第一个操作是什么,都不能重排序。这个规则确保volatile写之前的操作不会被编译器重排序到volatile写之后。

当第一个操作是volatile读时,不管第二个操作是什么,都不能重排序。这个规则确保volatile读之后的操作不会被编译器重排序到volatile读之前。

当第一个操作是volatile写,第二个操作是volatile读时,不能重排序。

为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。对于编译器来说,发现一个最优布置来最小化插入屏障的总数几乎不可能,为此,JMM采取保守策略。下面是基于保守策略的JMM内存屏障插入策略:

在每个volatile写操作的前面插入一个StoreStore屏障。

在每个volatile写操作的后面插入一个StoreLoad屏障。

在每个volatile读操作的后面插入一个LoadLoad屏障。

在每个volatile读操作的后面插入一个LoadStore屏障。

上述内存屏障插入策略非常保守,但它可以保证在任意处理器平台,任意的程序中都能得到正确的volatile内存语义。

下面是保守策略下,volatile写插入内存屏障后生成的指令序列示意图:

上图中的StoreStore屏障可以保证在volatile写之前,其前面的所有普通写操作已经对任意处理器可见了。这是因为StoreStore屏障将保障上面所有的普通写在volatile写之前刷新到主内存。

这里比较有意思的是volatile写后面的StoreLoad屏障。这个屏障的作用是避免volatile写与后面可能有的volatile读/写操作重排序。因为编译器常常无法准确判断在一个volatile写的后面,是否需要插入一个StoreLoad屏障(比如,一个volatile写之后方法立即return)。为了保证能正确实现volatile的内存语义,JMM在这里采取了保守策略:在每个volatile写的后面或在每个volatile读的前面插入一个StoreLoad屏障。从整体执行效率的角度考虑,JMM选择了在每个volatile写的后面插入一个StoreLoad屏障。因为volatile写-读内存语义的常见使用模式是:一个写线程写volatile变量,多个读线程读同一个volatile变量。当读线程的数量大大超过写线程时,选择在volatile写之后插入StoreLoad屏障将带来可观的执行效率的提升。从这里我们可以看到JMM在实现上的一个特点:首先确保正确性,然后再去追求执行效率。

下面是在保守策略下,volatile读插入内存屏障后生成的指令序列示意图:

上图中的LoadLoad屏障用来禁止处理器把上面的volatile读与下面的普通读重排序。LoadStore屏障用来禁止处理器把上面的volatile读与下面的普通写重排序。

上述volatile写和volatile读的内存屏障插入策略非常保守。在实际执行时,只要不改变volatile写-读的内存语义,编译器可以根据具体情况省略不必要的屏障。下面我们通过具体的示例代码来说明:

class VolatileBarrierExample {

int a;

volatile int v1 = 1;

volatile int v2 = 2;

void readAndWrite() {

int i = v1; //第一个volatile读

int j = v2; // 第二个volatile读

a = i + j; //普通写

v1 = i + 1; // 第一个volatile写

v2 = j * 2; //第二个 volatile写

}

… //其他方法

}

针对readAndWrite()方法,编译器在生成字节码时可以做如下的优化:

注意,最后的StoreLoad屏障不能省略。因为第二个volatile写之后,方法立即return。此时编译器可能无法准确断定后面是否会有volatile读或写,为了安全起见,编译器常常会在这里插入一个StoreLoad屏障。

上面的优化是针对任意处理器平台,由于不同的处理器有不同“松紧度”的处理器内存模型,内存屏障的插入还可以根据具体的处理器内存模型继续优化。以x86处理器为例,上图中除最后的StoreLoad屏障外,其它的屏障都会被省略。

前面保守策略下的volatile读和写,在 x86处理器平台可以优化成:

前文提到过,x86处理器仅会对写-读操作做重排序。X86不会对读-读,读-写和写-写操作做重排序,因此在x86处理器中会省略掉这三种操作类型对应的内存屏障。在x86中,JMM仅需在volatile写后面插入一个StoreLoad屏障即可正确实现volatile写-读的内存语义。这意味着在x86处理器中,volatile写的开销比volatile读的开销会大很多(因为执行StoreLoad屏障开销会比较大)。

二 Volatile的字节码

为了搞清楚内存屏障,我们扒开class字节码看一下,用javap -v -p class文件名(不要.class 后缀)运行

volatile int v1;

descriptor: I

flags: ACC_VOLATILE

.....

void readAndWrite();

descriptor: ()V

flags:

Code:

stack=3, locals=3, args_size=1

0: aload_0

1: getfield #52 // Field v1:I

4: istore_1

5: aload_0

6: getfield #54 // Field v2:I

9: istore_2

10: aload_0

11: iload_1

12: iload_2

13: iadd

14: putfield #72 // Field a:I

17: aload_0

18: iload_1

19: iconst_1

20: isub

21: putfield #52 // Field v1:I

24: aload_0

25: iload_2

26: iload_1

27: imul

28: putfield #54 // Field v2:I

31: return

除了其变量定义的时候有一个Volatile外,之后的字节码跟有无Volatile完全一样,于是我们又扒了下汇编代码

三 Volatile的汇编码

为了看到汇编码,要使用hsdis插件, 在mac系统下需要安装一个hsdis-amd64.dylib的插件。在网上找了一个,地址在这里。

下载下来后,将其放置到你的jre lib目录下即可。

mac系统上命令如下,

sudo mv ./hsdis-amd64.dylib /Library/Java/JavaVirtualMachines/jdk1.8.0_31.jdk/Contents/Home/jre/lib

然后再运行

java -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -Xcomp -XX:CompileCommand=dontinline,*VolatileBarrierExample.readAndWrite -XX:CompileCommand=compileonly,*VolatileBarrierExample.readAndWrite com.earnfish.VolatileBarrierExample > out.put

其中*VolatileBarrierExample.readAndWrite表示你运行的类.函数, com.earnfish.VolatileBarrierExample表示你的包名.类名,注意需要有main函数来运行你所要执行的函数。得出汇编码如下

0x000000011214bb49: mov %rdi,%rax

0x000000011214bb4c: dec %eax

0x000000011214bb4e: mov %eax,0x10(%rsi)

0x000000011214bb51: lock addl $0x0,(%rsp) ;*putfield v1

; - com.earnfish.VolatileBarrierExample::readAndWrite@21 (line 35)

0x000000011214bb56: imul %edi,%ebx

0x000000011214bb59: mov %ebx,0x14(%rsi)

0x000000011214bb5c: lock addl $0x0,(%rsp) ;*putfield v2

; - com.earnfish.VolatileBarrierExample::readAndWrite@28 (line 36)

其对应的Java代码如下

v1 = i - 1; // 第一个volatile写

v2 = j * i; // 第二个volatile写

可见其本质是通过一个lock指令来实现的。那么lock是什么意思呢?

查询IA32手册,它的作用是使得本CPU的Cache写入了内存,该写入动作也会引起别的CPU invalidate其Cache。所以通过这样一个空操作,可让前面volatile变量的修改对其他CPU立即可见。

所以,它的作用是

锁住主存

任何读必须在写完成之后再执行

使其它线程这个值的栈缓存失效

类似于前面是storestore,后面是storeload

java volatile内存屏障_从汇编看Volatile的内存屏障相关推荐

  1. java 内存分布_一图看懂JVM内存分布,永久记住!

    经常在说JVM内存分布,也经常去看,但是总是在面试的时候说不清楚或者模糊,甚至有可能说错,只有真正的理解,并且在心中有一个总结构图才能记得清楚说的清楚! | JVM总览图 java内存区域主要分程序计 ...

  2. 从汇编看volatile与MESI的关系

    JMM 1.来谈谈JMM 2.volatile和synchronized关键字 2.1volatile是如何保证可见性的?? 2.2 volatile是如何保证有序性的呢?? 哪些情况不能重排序?? ...

  3. java 堆内存结构_基于JDK1.8的JVM 内存结构【JVM篇三】

    在我的上一篇文章别翻了,这篇文章绝对让你深刻理解java类的加载以及ClassLoader源码分析[JVM篇二]中,相信大家已经对java类加载机制有一个比较全面的理解了,那么类加载之后,字节码数据在 ...

  4. Linux内存管理:一个故事看懂CPU内存管理技术

    目录 8086 32位时代 虚拟内存 分页交换 现在 往期热门回顾 推荐阅读 还记得我吗,我是阿Q,CPU一号车间的那个阿Q. 今天忙里偷闲,来到厂里地址翻译部门转转,负责这项工作的小黑正忙得满头大汗 ...

  5. mysql内存淘汰_从创建索引过程中内存变化来看SQL Server与MySQL的内存淘汰算法

    在sqlserver中,几年之前就注意到一个现象:sqlserver中对一个大表创建索引或者rebuild索引的过程中,会引起内存剧烈的动荡,究其原因为何,这种现象到底正不正常,是不是sqlserve ...

  6. c++ thread 内存泄漏_深入剖析ThreadLocal原理、内存泄漏及应用场景

    本文主要针对JDK1.8讲解 ThreadLocal作用 先看一个简单的示例,创建两个线程,第一个线程向ThreadLocal中写入数据,第二个线程等待第一个线程完成从ThreadLocal中读取数据 ...

  7. linux 内存密码_您的密码错误是内存问题

    linux 内存密码 Let's play a game. Look at this string of characters for a minute and then see if you can ...

  8. python程序占用内存高_如何优化Python占用的内存,面试必学

    如果程序处理的数据比较多.比较复杂,那么在程序运行的时候,会占用大量的内存,当内存占用到达一定的数值,程序就有可能被操作系统终止,特别是在限制程序所使用的内存大小的场景,更容易发生问题.下面我就给出几 ...

  9. kettle 内存设置_【转】kettle 的内存设置及输出日志的时间类型

    本文转载自:http://blog.csdn.net/dqswuyundong/archive/2010/10/19/5952004.aspx 设置kettle的内存 REM ************ ...

最新文章

  1. 「Python」pycharm多项目虚拟环境切换
  2. 简单的通讯录程序系统python
  3. leetcode83,删除有序链表中的重复元素
  4. Python实现红黑树的插入操作
  5. sql服务器密码如何显示,如何查看sql数据库密码
  6. 按键精灵--VS挤房器_Fly_v2.5版
  7. web系统服务器登录不上去,宝塔面板严重错误登录不上怎么办
  8. android 仿微信demo————微信顶部操作栏搜索按钮实现(查询通讯录好友功能)
  9. 编程入门书籍:大学学习计算机基础必读 5 本经典入门书籍,收藏
  10. 印象最深刻的三位老师、难忘的往事
  11. python求解迷宫问题,配js实现的走迷宫动画,动起来才有意思~
  12. HRM人力资源系统-Day13
  13. 思途旅游CMS短信宝短信插件
  14. 主动降噪耳机哪个好?2021年双11主动降噪耳机推荐!
  15. 【js学习笔记三十九】简单工厂模式
  16. Android Java 必备:Socket通信
  17. 计算机在英语课堂的应用,浅谈信息化技术在英语课堂的应用
  18. 使用Stream处理Map
  19. COMSOL Multiphysics弱形式入门(一)
  20. 录屏软件哪个好?我推荐好用且免费的野葱

热门文章

  1. Excel-VBA 快速上手(十、提示框、可输入的弹出框)
  2. Android RxJava操作符的学习---组合合并操作符---联合判断多个事件
  3. 《理解矩阵》——转载自孟岩老师
  4. Nginx location相关配置说明
  5. 明日方舟统计寻访工具【附下载链接】
  6. 论epoll的使用 - 高调coding,低调做人 - C++博客
  7. 照片太大上传不了怎么缩小?
  8. 网易云音乐的三点借鉴:功能、设计、运营
  9. 单例模式 懒汉式与恶汉式
  10. 保障MySQL安全的十四个最佳方法