博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
jvm内存模型
阅读量:2429 次
发布时间:2019-05-10

本文共 3085 字,大约阅读时间需要 10 分钟。

java内存模型

Java虚拟机试图定义一种内存模型(Java Memory Model, JMM)来屏蔽各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果。

主内存与工作内存

Java内存模型规定了所有的变量都存储在主内存中,线程的工作内存保存了该线程使用到的变量的主存拷贝副本,线程对变量的所有操作(读取,赋值)都必须在工作内存中进行,而不能直接读写主存中的变量,线程间变量值的传递均需要主内存来完成,线程,主存,工作内存的交互关系如下:
这里写图片描述
举个简单的例子:

i = 10

执行线程必须在自己的工作内存对i的副本进行赋值,然后才能回写到主存中。

内存间交互操作

Java定义了8种内存操作:

  • lock(锁定):作用于主内存的变量,它把一个变量标识为一个线程独占的状态。
  • unlock(解锁):作用于主内存的变量,它把一个处于锁定状态下的变量释放出来,释放后的变量才能被其它线程锁定。
  • read(读取):作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load使用。
  • load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
  • use(使用):作用于工作内存中的变量。它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作。
  • assign(赋值):作用于工作内存的变量。它把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
  • store(存储):作用于工作内存的变量,它把工作内存中一个变量的值传送到主内存中,以便随后的write使用。
  • write(写入):作用于住内存的变量。它把store操作从工作内存中得到的变量的值放入到主内存的变量中。

Java内存模型还规定了执行上述8种操作时需要满足的规则:

  • read和load,store和write操作必须成对出现,即不允许一个变量从主内存读取了但工作内存不接受,或者从工作内存发起了回写但主内存不接受的情况出现。
  • 不允许一个线程丢弃它最近的assign操作,即变量在工作内存中改变了必须把该变化同步回主存。
  • 不允许一个线程无原因的(没有发生过任何assign操作)把数据从线程的工作内存同步回主内存中。
  • 一个新的变量只能在主存中诞生,不允许在工作内存中直接使用一个未被初始化的变量。换句话说就是对一个变量实施use,store操作之前,必须先执行assign和load操作。
    • 一个变量在同一时刻只允许一条线程对其进行lock操作,但lock操作可以被同一线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁。
    • 如果对一个变量执行lock操作,那将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行load或assign操作初始化变量的值。
    • 如果一个变量没有被lock操作锁定,那就不允许对它执行unlock操作,也不允许unlock被其他线程锁定的变量。
    • 对一个变量执行unlock操作前,必须先把此变量同步回主存中(执行store,write操作)。

原子性,可见性,有序性

Java的内存模型是围绕并发中如何处理原子性,可见性和有序性这三个特征建立的。

  • 原子性

    原子性指一个或者多个操作要么都不执行,要么全部被执行,也就是不能被终端。Java内存模型来保证的原子操作有read,load,assign,use,store和write。基本所有的基本数据类型的读写操作可以认为是原子的。
    如果一个应用场景需要更大范围的原子操作,java内存模型提供了lock和unlock操作,反映到java代码就是synchronized。

  • 可见性

    可见性指当以个线程修改了共享变量的值,其他线程能够立即得到这个修改。Java中是通过volatile关键字来实现可见性的,volatile保证变量在修改后立即会刷新到主存中。
    除了volatile之外,Java还提供了两个关键字实现可见性,synchronized和final。同步块的可见性由“对一个变量执行unlock前,必须先把此变量同步回主存(执行store,write操作)”这条规则获得的,而final关键字的可见性:被final修饰的字段在构造器中一旦初始化完成,并且构造器没有把this引用传递出去,那么其他线程就可以看见final字段的值。

  • 有序性

有序性即程序执行的顺序按照代码的先后顺序执行。看下面的代码:

int i = 0;              	boolean flag = false;	i = 1;                //语句1  	flag = true;          //语句2

上面代码定义了一个int型变量,定义了一个boolean类型变量,然后分别对两个变量进行赋值操作。从代码顺序上看,语句1是在语句2前面的,那么JVM在真正执行这段代码的时候会保证语句1一定会在语句2前面执行吗?不一定,为什么呢?这里可能会发生指令重排序(Instruction Reorder)。

下面解释一下什么是指令重排序,一般来说,处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。
Java内存模型中允许编译器和处理器进行指令重排,但是重排不会影响影响单线程的执行,确会影响到多线程并发执行的正确性。看下面的例子:

//线程1	context = loadContext();  //语句1	inited = true;            //语句2	//线程2	while (! inited) {		sleep();	}	doSomethingwithconfig();

上面代码中,由于语句1和语句2没有数据依赖性,因此可能会被重排序。假如发生了重排序,在线程1执行过程中先执行语句2,而此是线程2会以为初始化工作已经完成,那么就会跳出while循环,去执行doSomethingwithconfig(context)方法,而此时context并没有被初始化,就会导致程序出错。

Java内存模型中存在着一些“天然”的先行发生关系,也就是所谓的happens-before原则。

  • 程序次序原则

    一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作。

  • 管程锁定规则

    一个unlock操作先行发生于同一个锁的lock操作。

  • volatile变量规则

    对一个变量的写操作先行发生于后面对这个变量的读操作。

  • 线程启动规则

    Thread对象的start方法先行发生于此线程的每一个动作。

  • 线程终止规则

    线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束,Thread.isAlive()的返回值等手段检测到线程已经终止执行。

  • 线程中断规则

    对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生。

  • 对象终结规则

    一个对象的初始化先行发生于它的finalize()方法。

  • 传递性

    如果操作A先行发生于操作B,而操作B又先行发生于操作C,则操作A先行发生于操作C。

总结

本文中主要从理论的角度讲解了Java内存模型,常用的内存操作,已经内存模型相关的特性。

你可能感兴趣的文章
JavaScript 爆红后,微软为何还要开发 TypeScript?
查看>>
软件开发行业,年轻与大龄程序员的生存现状
查看>>
王者荣耀活动精选 Blink 第二弹来袭!
查看>>
打开数“智”化之门,一字之差带来的思考
查看>>
阿里技术人的成长路径是什么?
查看>>
你值得拥有!更省钱地完成数据监听
查看>>
漫画 | TCP,一个悲伤的故事
查看>>
张一鸣无圈胜破圈?
查看>>
抓紧!抓紧!CSDN年终重榜福利来了~人手一份,快来投稿!!
查看>>
干货! AI 推断解决方案栈 Vitis AI 全流程独家解析
查看>>
真相了 | 敲代码时,程序员戴耳机究竟在听什么?
查看>>
回首互联网十年,我们能从八次烧钱大战中学到什么
查看>>
漫画:如何辨别二逼互联网公司!?
查看>>
麒麟信安面向场景化创新,赋能openEuler商业验证
查看>>
王者又连跪了?快让 AI 帮你上分!
查看>>
1 分钟带你认识从 "�" 到 "锟斤拷"
查看>>
3 年培养 10 万“码农”,郑州推出“码农计划”
查看>>
一个三本程序猿的大厂逆袭之路
查看>>
程序员弃码投中医?还做成了不错的生意! | 极客视频
查看>>
百度一 29 岁程序员因“篡改数据”被抓
查看>>