jvm内存模型

13 篇文章 0 订阅
10 篇文章 0 订阅

java面试题网站:www.javaoffers.com

  • jvm内存模型
    • 主内存和工作内存
    • 内存间的交互操作
    • 对volatile的特殊规则
    • 对long和double的特殊规则
    • 原子性,可见性和有序性
    • 先行发生原则
  • jvm内存模型的作用(为什么要定义内存模型)
1:用来屏蔽硬件和各种操作系统之间内存访问的差异。可以让Java程序在各种不同硬件的操作系统上具有一致的访问效果。(外话: 这也就是为什么说,java具有具有跨平台性,因为不同平台jvm)。
2:java内存模型主要通过定义内存访问规则来屏蔽硬件和操作系统之间内存访问的差异。内存访问规则通常指实例变量,静态变量,数组对象元素在内存中(这里指堆内存)访问的规则。注意因为局部变量和方法参数
   属于线程私有(存放在局部变量表)所以不包含。这里说是变量的值,并非变量引用的对象。
3:根据java内存模型定义在jvm内部划分 堆和栈 内存。    
  • 主内存和工作内存
主内存:通常理解为RAM(硬件内存),但是此时它属于jvm的内存。 工作内存通常理解为cpu的高速缓存区或寄存器。jvm模型规定所有的变量都存入主内存. 注意这里的主内存和工作内存和jvm中的堆,
栈不是同一个意思。这里的主内存是站在jvm虚拟机的角度去看,jvm虚拟机本质上是由其他语言所编写的应用程序。通过jvm内存模型将jvm虚拟机的主内中划分出两种内存概念,一种是堆内存,另外一种是栈内存。
这两种内存和主内存(Ram),线程私有内存(高速缓存区/寄存器)的关系如下图:
注意: 关于主内存,工作内存,jvm堆内存,jvm栈内存这些概念参考了:java虚拟机:jvm高级特性和最佳实践(周志明),java虚拟机规范
和 国外博客 http://tutorials.jenkov.com/java-concurrency/java-memory-model.html

在这里插入图片描述

  • 内存之间的交互
主要定义变量从工作内存copy到主内存,然后从主内存再回写(同步)到主内存的一下操作规则。java内存模型定义了8中操作,并且每一种操作都具有原子性(不可在分)。注意:double.long类型在
read,load,store,write这几个操作会在不同的jvm中有不同(有可能不具有原子性)。这8中操作图如下:

在这里插入图片描述

8中操作的基本定义:
lock      : 作用于主内存,将主内存的变量改为锁状态。
unlock: 作用于主内存,将主内存的变量改为无锁状态,这样其他线程才能使用。
read     : 作用于主内存,将主内存变量的值读取(copy)到工作内存中。为了接下来的load操作
load     : 作用于工作内存,将read后的数据(此时在工作内存中),放入到工作内存中的变量副本中
use       :  作用于工作内存,将数据和指令发送给执行器 
assign : 作用于工作内存,将执行器的执行结果回写给高速缓存区的变量
store    : 作用于工作内存,将修改后的变量回写到主内存
write    : 作用于主内存变量,将工作内存中store的值回写的值放入到对应变量中

注意点: 在这8个原子操作中,(read,load) 和(store,write)必须按照这个顺序,并且不允许单独出现某一个操作。但是在保证顺序的情况下中间允许其他指令。不允许线程丢弃最近的assign操作。
指修改后的数据一定要回写到主内存中。不允许将没有经过assign操作的数据回写主内存。一个变量必须在主内存中初始化后才能read load等操作。同一个变量只能被一个线程lock,unlock。在unlock
执行时必须是保证store,write已经执行(实际是保证数据从工作内存回写主内存后才能unlock)。
  • 对volatile特殊规则
volatile 指将数据尽快刷入主内存,所以可以理解为java最轻量的同步机制。但是他不能保证是线程安全的,因为java中的运算并不是原子的,比如一个 a=b+c;编译后的字节码为: 
iload_2,iload_1,iadd,istore_3 ,其次是一条字节码可能会转换为多条机器码码(机器码具有原子性)。volatile有两种特性:
1:被volatile声明的变量可以其他线程看到,注意:解决这个问题也可以用java的锁
2:禁止'指令重排序优化'。指令重排序后依然能保证当前线程返回正确的执行结果,但是如果设计到多线成就会产生状态不一致,状态指多线程逻辑场景,例如:
parse();//解析数据并放入数据库
boolean isOk = true;//改为ture,另外的一个线程对其进行轮寻检查如果为true则开始查询数据库。
这两行代码逻辑没有关联性,但是有可能业务有。如果这两行代码被重排序后:
boolean isOk = true;
parse();
此时有肯能,在parse还没有执行,其他线程已经检测到isOk为true则开始查询数据,那么就会出现一种不是我们想要的结果。

  • 对long和double的特殊规则
java内存模型要求 lock,read.load,use,assign,store,write,unlock 这8个操作要具有原子性,但是针对于64位的long和double类型则相对宽松一点,
如果没有被volatile声明的long或double类型则允许读取32位(在并发情况下,),否则必须读取64位。所以当long,double类型被volatile声明时则会具有原子性,注意现在的jvm虚拟机大部分
都已经将long和double进行了原子操作。如果你知道自己使用的jvm虚拟机支持long和double操作,则编写代码的时候可以不写volatile。如果不知道但又想让其具有原子性则可以
显示声明volatile
  • 原子性,可见性,有序性
原子性:jvm内存模型中的8大原子操作具有原子性,'同步代码块(syncronized,lock--unlock)'在多线程环境中具有原子性。因为具有锁性质的代码块在并发多线程环境中只能被一个线程所执行。
所以同步代码块具有原子性,不可再分。

可见性:volatile,syncronized,lock--unlock 具有可见性。 volatile可以是被修改的数据及时的写回主内存,并通知其他线程的高速缓存区中的数据失效,需要重新从主内存中读取。因此产生
一种效果是:被volatile修饰的变量在被修改后其他线程是可见的(因为重读了)。syncronized,lock--unlock 是具有锁的性质,一个原子变量(可以理解为基本类型,因为任何一个类都是由基本类型
进行支撑,比如String 内部是char[], Integer 内部是char[]和int.当然有些类中含有native,这种不考虑)在具有lock和unlock原子操作下,那么读必须在unlock之后,所以也就在store和write
原子操作之后。因此每次读到的都是其他线程写完之后的,因此就会产生一种可见性的效果。

有序性:volatile 禁止指令重排序所以具有有序性。syncronized,lock--unlock会使代码块具有串行执行的性质,同一个同步代码块在多线程环境下具有串行执行的性质(每一个线程都需要等持有锁的
线程释放锁之后才能去竞争锁,获取到锁的线程才能执行同步代码块,所以就会产生一种效果:同步代码是被一个接一个(串行)的线程获取锁后执行的。),因此具有有序性

  • 先行发生原则
是指程序中的操作之间存在偏序关系。偏序可以简单理解为在操作之间存在先后的执行顺序。所以'先行发生原则'是规定程序操作之间的执行逻辑顺序。例如:
int a = 1;
int b = a;
他们之间在逻辑上是有关系的。所以他们必须要顺序执行。再看下面你的例子:
int a = 1;
int b = 1;
这个例子中的a和b是没有关系的,所以jvm有可能会进行指令重排序。先执行b,然后再执行a. 假如业务是:有一个线程Thread1在初始化 a和b. Thread2 在不停的对b进行读取并判断,如果b等于1则读取a的值并把它写入数据库,
此时线程Thread1发生了指令重排序就会导致b先执行,然后线程Thread2发现b等于1,就开始读取a, 但是Thread1还没有来得及初始化a,则线程Thread2读取b的值为0(int默认值为0),然后就会把0 写入数据库。在这种情况下
就会造成数据错误(数据库应该存放1,但是存放了0,解决这个问题就是避免指令重拍序,使用volatile或则锁)。先行发生原则本质上就是定义一套操作在执行时先发生和后发生的一些规则。
这些规则如下:

1.程序次序规则(Program Order Rule):在一个线程内,按照程序代码顺序,书写在前面的操作先行发生于书 写在后面的操作。准确地说,应该是控制流顺序而不是程序代码顺序,因为要考虑分支、循环等结构。
2.管程锁定规则(Monitor Lock Rule): 一个unlock操作先行发生于后面对同一个锁的lock操作。这里必须强 调的是同一个锁,而“后面”是指时间上的先后顺序。
3.volatile变量规则(Volatile Variable Rule):对一个volatile变量的写操作先行发生于后面对这个变量的 读操作(本质就是写后重读),这里的“后面”同样是指时间上的先后顺序。

4.线程启动规则(Thread Start Rule) : Thread对象的start ()方法先行发生于此线程的每一个动作。
5.线程终止规则(Thread Termination Rule):线程中的所有操作都先行发生于对此线程的终止检测,我们可 以通过Thread, join ()方法结束、Thread. isAlive ()的返回值等手段检测到线程已经终止执行。
6.线程中断规则(Thread Interruption Rule):对线程interrupt ()方法的调用先行发生于被中断线程的代 码检测到中断事件的发生,可以通过Thread.interrupted ()方法检测到是否有中断发生。

7.对象终结规则(Finalizer Rule): 一个对象的初始化完成(构造函数执行结束)先行发生于它的 finalize ()方法的开始。
8.传递性(Transitivity):如果操作A先行发生于操作B,操作B先行发生于操作C,那就可以得出操作A先行发 生于操作C的结论。
  • 参考书籍: JVM虚拟机:高级特性与最佳实践, Java虚拟机规范.Java SE 8版, 计算机组成原理(第四章:4.4高速缓冲储存器),深入浅出DPDK第二章:Cache和内存
  • 参考博客: 国外博客 http://tutorials.jenkov.com/java-concurrency/java-memory-model.html
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值