热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

深入理解JVM之Java内存模型tobecontinued...

要了解Java内存模型,首先我们要了解什么是Java内存模型,它有什么作用?描述Java内存模型(简称:JMM)的规范提案JSR-133标题《JavaMemoryModelandT


要了解Java内存模型,首先我们要了解什么是Java内存模型,它有什么作用?


描述Java内存模型(简称:JMM)的规范提案JSR-133标题《Java Memory Model and Thread Specification》,通过这个标题,可以看出JMM是和线程相关的规范。此规范地指定的 JMM Web Site 上对规范的说明如下:


The Java Memory Model defines how threads interact through memory.


通过以上描述,说明JMM规范主要是解决在多线程场景下线程间如何通信。


硬件内存架构


要了解JMM,我们先来从硬件角度,看看多核CPU场景下,多线程程序会存在什么问题。



如上图所示,在多核(多CPU)硬件架构中,系统中有两个CPU,分布运行了一个线程,对象obj保存在主内存(RAM)中。由于RAM的速度远低于CPU,为了加快数据的访问,当CPU(线程)需要使用obj对象时,会预先把obj对象加载到CPU的缓存(CPU Cache)中,处理完毕后,再把对obj对象的更新回写到到RAM。


每个CPU有自己独立的缓存,一个CPU无法访问其他CPU的缓存,也就是CPU间无法直接交换数据,CPU间所有的数据交换都需要借助主内存来完成。


假设线程执行的是 +1 操作。在上图示例中,两个线程并发执行。初始状态,主内存中obj.num=1;线程1先读取了obj对象,并执行+1操作,结果obj.num=2;在线程1的修改还未从CPU缓存回写到主内存的时候,线程2从主内存中读取了obj对象,此时线程2读取到的obj.num=1;此后,线程1和线程2分别把obj回写到主内存;按正常业务逻辑,obj.num被+1了两次,结果应该是3,但上述情况,最终主内存中obj.num=2。这是因为两个线程对数据并发访问冲突导致线程读到的数据不一致。


Java内存模型


Java是平台无关的语言,为了实现跨平台运行,Java虚拟机(JVM)上运行的是Java字节码(Java bytecode)。Java内存模型(Java Memory Model,JMM)是Java虚拟机规范定义的,用来屏蔽掉Java程序在各种不同的硬件和操作系统对内存的访问的差异,实现Java程序在各种不同的平台上都能达到内存访问的一致性。和硬件内存架构类似,JMM把内存分为 主内存工作内存 ,主内存由所有线程共享,工作内存为线程私有。


JMM规范主要定义程序变量操作的规则,规范中定义的主内存、工作内存的概念和JVM运行时内存分区中定义的堆、栈区域不是同一纬度的概念,不能互相对应,不过为了便于理解,可把主内存类比为堆,工作内存类比为栈。


虽然工作内存和栈可以类比,但两者是不同的概念。


JMM管理的程序变量,主要是指在对象实例字段、静态字段、构成数组字段的元素等,不包括方法参数、方法局部变量等保存在栈里的变量,因为栈本身就是线程私有的,并不存在线程一致性问题。


JMM规范规定所有的变量都要在主内存中产生,而线程不允许直接操作主内存中的变量,线程需要把变量副本拷贝到工作线程中进行操作,操作完后再回写到主内存。



主内存


JMM规定所有的变量都必须在主内存中产生。


工作内存


JVM中每个线程都有自己的工作内存,是线程私有的,可以类比CPU的高速缓存。线程的工作内存保存了线程需要的变量在主内存中的副本。


数据交互接口


JMM中定义了8个用于主内存和工作内存见数据互操作的接口,用于在两者间传输数据,这些操作都是原子性的。



  1. lock(锁定)
    作用于主内存变量,属于互斥锁,一个变量同时只能一个线程锁定

  2. unlock(解锁)
    作用于主内存变量,lock的反操作,释放变量的锁

  3. read(读取)
    作用于主内存变量,表示把一个主内存变量的值传输到线程的工作内存,以便随后的load操作使用

  4. load(载入)
    作用于线程工作内存变量,表示把read操作从主内存中读取的变量的值放到工作内存的变量副本中

  5. use(使用)
    作用于线程工作内存变量,表示把工作内存中的一个变量的值传递给字节码指令

  6. assign(赋值)
    作用于线程工作内存变量,表示把字节码指令执行返回的结果赋值给工作内存中的变量,字节码赋值操作

  7. store(存储)
    作用于线程工作内存变量,把工作内存中的一个变量的值传递给主内存,以便随后的write操作使用

  8. write(写入)
    作用于主内存变量,把store操作从工作内存中得到的变量的值放入主内存的变量中


数据交互原则



  1. 变量只能在主内存中产生。

  2. 线程对主内存变量的操作必须在线程的工作内存中进行,不能直接操作主内存中的变量。

  3. 不同的线程之间也不能相互访问对方的工作内存。线程之间需要传递变量的值,必须通过主内存来作为中介进行传递。

  4. read和load操作、store和write必须成对使用,即:不允许从主内存中读取了变量,工作内存不接收,或者工作内存回写了变量,主内存不接收。

  5. assign操作后的变量必须回写到主内存。

  6. 不允许回写没有修改(即未assign)的变量到主内存。

  7. 一个变量同时只能被一个线程对其进行lock操作,但是同一个线程对一个变量加锁后,可以继续加锁,同时在释放锁的时候释放锁次数必须和加锁次数相同。

  8. 对变量执行lock操作,就会清空工作空间该变量的值,使用时需要重新读取;对一个变量执行unlock之前,必须先把变量同步回主内存中。


内存并发一致性原则


上述的内存并发一致性问题,在JMM中定义了三个原则来避免,分别是原子性、可见性和有序性。


原子性


可见性


可见性是指一个线程修改了一个变量的值后,其他线程立即可以读取到这个变量的最新值。


有序性


为了使处理器内部运算单元尽可能被充分利用,处理器还会对输入的代码进行乱序执行(Out-Of-Order Execution)优化,处理器会在乱序执行之后的结果进行重组,保证结果的正确性,也就是保证结果与顺序执行的结果一致。但是在真正的执行过程中,代码执行的顺序并不一定按照代码的书写顺序来执行,可能和代码的书写顺序不同。


参考资料



  1. JSR-133: JavaTM Memory Model and Thread Specification

  2. The Java Memory Model




推荐阅读
  • 深入理解Kafka服务端请求队列中请求的处理
    本文深入分析了Kafka服务端请求队列中请求的处理过程,详细介绍了请求的封装和放入请求队列的过程,以及处理请求的线程池的创建和容量设置。通过场景分析、图示说明和源码分析,帮助读者更好地理解Kafka服务端的工作原理。 ... [详细]
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • 嵌入式处理器的架构与内核发展历程
    本文主要介绍了嵌入式处理器的架构与内核发展历程,包括不同架构的指令集的变化,以及内核的流水线和结构。通过对ARM架构的分析,可以更好地理解嵌入式处理器的架构与内核的关系。 ... [详细]
  • 本文介绍了操作系统的定义和功能,包括操作系统的本质、用户界面以及系统调用的分类。同时还介绍了进程和线程的区别,包括进程和线程的定义和作用。 ... [详细]
  • Android自定义控件绘图篇之Paint函数大汇总
    本文介绍了Android自定义控件绘图篇中的Paint函数大汇总,包括重置画笔、设置颜色、设置透明度、设置样式、设置宽度、设置抗锯齿等功能。通过学习这些函数,可以更好地掌握Paint的用法。 ... [详细]
  • 开发笔记:计网局域网:NAT 是如何工作的?
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了计网-局域网:NAT是如何工作的?相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • 本文讨论了在openwrt-17.01版本中,mt7628设备上初始化启动时eth0的mac地址总是随机生成的问题。每次随机生成的eth0的mac地址都会写到/sys/class/net/eth0/address目录下,而openwrt-17.01原版的SDK会根据随机生成的eth0的mac地址再生成eth0.1、eth0.2等,生成后的mac地址会保存在/etc/config/network下。 ... [详细]
  • Redis底层数据结构之压缩列表的介绍及实现原理
    本文介绍了Redis底层数据结构之压缩列表的概念、实现原理以及使用场景。压缩列表是Redis为了节约内存而开发的一种顺序数据结构,由特殊编码的连续内存块组成。文章详细解释了压缩列表的构成和各个属性的含义,以及如何通过指针来计算表尾节点的地址。压缩列表适用于列表键和哈希键中只包含少量小整数值和短字符串的情况。通过使用压缩列表,可以有效减少内存占用,提升Redis的性能。 ... [详细]
  • 本文介绍了Swing组件的用法,重点讲解了图标接口的定义和创建方法。图标接口用来将图标与各种组件相关联,可以是简单的绘画或使用磁盘上的GIF格式图像。文章详细介绍了图标接口的属性和绘制方法,并给出了一个菱形图标的实现示例。该示例可以配置图标的尺寸、颜色和填充状态。 ... [详细]
  • 合并列值-合并为一列问题需求:createtabletab(Aint,Bint,Cint)inserttabselect1,2,3unionallsel ... [详细]
  • ***byte(字节)根据长度转成kb(千字节)和mb(兆字节)**parambytes*return*publicstaticStringbytes2kb(longbytes){ ... [详细]
  • 本文整理了Java面试中常见的问题及相关概念的解析,包括HashMap中为什么重写equals还要重写hashcode、map的分类和常见情况、final关键字的用法、Synchronized和lock的区别、volatile的介绍、Syncronized锁的作用、构造函数和构造函数重载的概念、方法覆盖和方法重载的区别、反射获取和设置对象私有字段的值的方法、通过反射创建对象的方式以及内部类的详解。 ... [详细]
  • 篇首语:本文由编程笔记#小编为大家整理,主要介绍了软件测试知识点之数据库压力测试方法小结相关的知识,希望对你有一定的参考价值。 ... [详细]
author-avatar
手机用户2702932821
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有