多线程之Java内存模型(JMM)(一)

在未正确使用锁的时候,多线程的程序可能变的很容易出错,并且难以排查。而JMM则给我们一种规范,它描述了多线程程序如何与内存交互。


与文无关

JMM大致描述:

  • JMM描述了线程如何与内存进行交互。Java虚拟机规范视图定义一种Java内存模型,来屏蔽掉各种操作系统内存访问的差异,以实现Java程序在各种平台下都能达到一致的访问效果。
  • JMM描述了JVM如何与计算机的内存进行交互
  • JMM都是围绕着原子性,有序性和可见性进行展开的

JMM的主要目标是定义程序中各个变量的访问规则,虚拟机将变量存储到内存和从内存取出变量这样的底层细节。此处的变量指在堆中存储的元素。

多线程的时候为什么容易出错?

Java内存模型规定所有的共享变量都存储在主内存中,而每条线程有自己的工作内存(本地内存),工作内存保存了共享变量的副本,而不同内存又无法访问对方的工作内存,所以如果线程在工作内存中修改了变量副本,其它线程是无从得知的。

线程的传值均需要通过主内存来完成

JMM模型

JMM模型

主内存与工作内存如何交互?

Java内存模型定义了8种操作来完成主内存与工作内存的交互细节,虚拟机必须保证这8种操作的每一个操作都是原子的,不可再分的。

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

执行上述8种基本操作的规则:

  • 不允许read和load,store和write操作之一单独出现。
  • 不允许一个线程丢弃它最近的assign操作。即变量在工作内存中改变了账号必须把变化同步回主内存
  • 一个新的变量只允许在主内存中诞生,不允许工作内存直接使用未初始化的变量。
  • 一个变量同一时刻只允许一条线程进行lock操作,但同一线程可以lock多次,lock多次之后必须执行同样次数的unlock操作
  • 如果对一个变量进行lock操作,那么将会清空工作内存中此变量的值。
  • 不允许对未lock的变量进行unlock操作,也不允许unlock一个被其它线程lock的变量
  • 如果一个变量执行unlock操作,必须先把次变了同步回主内存中。

这8种操作定义相当严禁,实践起来又比较麻烦,但是可以有助于我们理解多线程的工作原理。有一个与此8种操作相等的Happen-before原则。

Happen-before原则

这个是Java内存模型下无需任何同步器协助就已经存在,可以直接在编码中使用。如果两个操作之间的关系不在此列,并且无法从下列规则推导出来的话,它们的顺序就没有保障,虚拟机可以对他们进行任意的重排。

天然的happen-before

  • 程序顺序原则:一个线程内包装语义的串行性
  • volatile变量的写,先发生于读,这保证了volatile变量的可见性
  • 锁规则:unlock先与lock
  • 传递性:A 先于B,B先于C,那么A必然先于C
  • 线程的start先于线程的每一个动作
  • 线程的所有操作优先于线程的终结(Thread.join())
  • 线程的中断(interupt)先于被中断线程的代码
  • 对象的构造函数执行,先于finalize()方法

Java运行时数据区

JVM定义了一些程序运行时会使用到的运行时数据区,其中一些会随着虚拟机启动而创建,随着虚拟机退出而销毁。另外一些是与现场一一对应的,这些线程对应的数据区会随着线程的开始和结束而创建和销毁。
这部分参考JVM规范

1. pc寄存器

可以支持多条线程同时允许,每一条Java虚拟机线程都有自己的pc寄存器。任意时刻,一条JVM线程之后执行一个方法的代码,这个方法被称为当前方法(current method)
如果这个方法不是native的,那么PC寄存器就保存JVM正在执行的字节码指令地址。
如果是native的,那么pc寄存器的值为undefined
pc寄存器的容量至少能保证一个returnAddress类型的数据或者一个平台无关的本地指针的值。

2. JVM Stack(虚拟机栈)
  • 每一个JVM线程都有自己的私有虚拟机栈,这个栈与线程同时创建,用于存储栈帧(Frame)。
  • 栈用来存储局部变量与一些过程结果的地方。在方法调用和返回中也扮演了很重要的角色。
  • 栈可以试固定分配的也可以动态调整
    • 如果请求线程分配的容量超过JVM栈允许的最大容量,抛出StackOverflowError异常
    • 如果JVM栈可以动态扩展,扩展的动作也已经尝试过,但是没有申请到足够的内存,则抛出OutofMemoryError异常
3. Heap(堆)

堆是可以可供各个线程共享的运行时存储区域,也是供所有类的实例和数组对象分配内存的区域。堆在JVM启动的时候创建。
堆所存储的就是被GC所管理的各种对象。
堆也是可以固定大小和动态调整的:
实际所需的堆超过的GC所提供的最大容量,那么JVM抛出OutofMemoryError异常。

4. Method Area(方法区)

也是各个线程共享的运行时内存区,它存储每一个类的实例信息,运行时常量池,字段和方法数据,构造函数和普通方法的字节码等内容。还有一些特殊方法。

方法区是堆的逻辑组成部分,也在JVM启动时创建,简单的JVM可以不实现这个区域的垃圾收集。

方法区也可固定大小和动态分配与堆一样,内存空间不够,那么JVM抛出OutofMemoryError异常。

5. Run-Time Constant Pool(运行时常量池)

在方法区中分配,在加载类和接口到虚拟机之后,就创建对应的运行时常量池。

它是class文件中每一个类或接口的常量池表的运行时表现形式。像字符串。Java的主要类型。

存储区域不够用时候抛出OutofMemoryError异常。

6. Native Method Stacks(原生方法栈或本地方法栈)

JDK中native的方法,System类和Thread类中有很多。使用C语言编写的方法,这个也通常叫做C stack。

可以不支持本地方法栈,但是如果支持的时候,这个栈一般会在线程创建的时候按线程分配。

与栈的错误一样,StackOverFlowError和OutOfMemeoryError.

一个案例
案例
  • 一个本地变量可能是原始类型,在这种情况下,它总是“呆在”线程栈上。
  • 一个本地变量也可能是指向一个对象的一个引用。在这种情况下,引用(这个本地变量)存放在线程栈上,但是对象本身存放在堆上。
  • 一个对象可能包含方法,这些方法可能包含本地变量。这些本地变量任然存放在线程栈上,即使这些方法所属的对象存放在堆上。
  • 一个对象的成员变量可能随着这个对象自身存放在堆上。不管这个成员变量是原始类型还是引用类型。
  • 静态成员变量跟随着类定义一起也存放在堆上。
  • 存放在堆上的对象可以被所有持有对这个对象引用的线程访问。当一个线程可以访问一个对象时,它也可以访问这个对象的成员变量。如果两个线程同时调用同一个对象上的同一个方法,它们将会都访问这个对象的成员变量,但是每一个线程都拥有这个本地变量的私有拷贝。
Java内存模型和硬件内存架构之间的对应

最后

这次主要讲了一些规则相关的东西,及Java中运行时数据存储的位置,建议看一下《深入理解JVM》最后一章。最好下载JSR-133规范对照着看。

参考:

  • Java内存模型
  • 《深入理解Java虚拟机》
  • 《Java高并发程序设计》
  • 《JVM specification》
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,458评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,454评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,171评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,062评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,440评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,661评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,906评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,609评论 0 200
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,379评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,600评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,085评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,409评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,072评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,088评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,860评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,704评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,608评论 2 270

推荐阅读更多精彩内容