广告
返回顶部
首页 > 资讯 > 后端开发 > Python >Java 内存模型(JVM)
  • 407
分享到

Java 内存模型(JVM)

2024-04-02 19:04:59 407人浏览 薄情痞子

Python 官方文档:入门教程 => 点击学习

摘要

目录前言一、什么是 Java 内存模型二、为什么需要 Java 内存模型三、顺序一致性内存模型四、Happens-Before 规则前言 在并发编程中,当多个线程同时访问同一个共享

前言

并发编程中,当多个线程同时访问同一个共享的可变变量时,会产生不确定的结果,所以要编写线程安全的代码,其本质上是对这些可变的共享变量的访问操作进行管理。导致这种不确定结果的原因就是可见性、有序性和原子性问题,Java 为解决可见性和有序性问题引入了 Java 内存模型,使用互斥方案(其核心实现技术是)来解决原子性问题。这篇先来看看解决可见性、有序性问题的 Java 内存模型(JMM)。

一、什么是 Java 内存模型

Java 内存模型定义如下:

内存模型限制的是共享变量,也就是存储在堆内存中的变量,在 Java 语言中,所有的实例变量、静态变量和数组元素都存储在堆内存之中。而方法参数、异常处理参数这些局部变量存储在方法栈帧之中,因此不会在线程之间共享,不会受到内存模型影响,也不存在内存可见性问题。

通常,在线程之间的通讯方式有共享内存和消息传递两种,很明显,Java 采用的是第一种即共享的内存模型,在共享的内存模型里,多线程之间共享程序的公共状态,通过读-写内存的方式来进行隐式通讯。

从抽象的角度来看,JMM 其实是定义了线程和主内存之间的关系,首先,多个线程之间的共享变量存储在主内存之中,同时每个线程都有一个自己私有的本地内存,本地内存中存储着该线程读或写共享变量的副本(注意:本地内存是 JMM 定义的抽象概念,实际上并不存在)。抽象模型如下图所示:

在这个抽象的内存模型中,在两个线程之间的通信(共享变量状态变更)时,会进行如下两个步骤:

  1. 线程 A 把在本地内存更新后的共享变量副本的值,刷新到主内存中。
  2. 线程 B 在使用到该共享变量时,到主内存中去读取线程 A 更新后的共享变量的值,并更新线程 B 本地内存的值。

JMM 本质上是在硬件(处理器)内存模型之上又做了一层抽象,使得应用开发人员只需要了解 JMM 就可以编写出正确的并发代码,而无需过多了解硬件层面的内存模型。

二、为什么需要 Java 内存模型

在日常的程序开发中,为一些共享变量赋值的场景会经常碰到,假设一个线程为整型共享变量 count 做赋值操作(count = 9527;),此时就会有一个问题,其它读取该共享变量的线程在什么情况下获取到的变量值为 9527 呢?如果缺少同步的话,会有很多因素导致其它读取该变量的线程无法立即甚至是永远都无法看到该变量的最新值。

比如缓存就可能会改变写入共享变量副本提交到主内存的次序,保存在本地缓存的值,对于其它线程是不可见的;编译器为了优化性能,有时候会改变程序中语句执行的先后顺序,这些因素都有可能会导致其它线程无法看到共享变量的最新值。

在文章开头,提到了 JMM 主要是为了解决可见性和有序性问题,那么首先就要先搞清楚,导致可见性和有序性问题发生的本质原因是什么?现在的服务绝大部分都是运行在多核 CPU 的服务器上,每颗 CPU 都有自己的缓存,这时 CPU 缓存与内存的数据就会有一致性问题了,当一个线程对共享变量的修改,另外一个线程无法立刻看到。导致可见性问题的本质原因是缓存

有序性是指代码实际的执行顺序和代码定义的顺序一致,编译器为了优化性能,虽然会遵守 as-if-serial 语义(不管怎么重排序,在单线程下的执行结果不能改变),不过有时候编译器及解释器的优化也可能引发一些问题。比如:双重检查来创建单实例对象。下面是使用双重检查来实现延迟创建单例对象的代码:



public class DoubleCheckedInstance {

  private static DoubleCheckedInstance instance;

  public static DoubleCheckedInstance getInstance() {
    if (instance == null) {
      synchronized (DoubleCheckedInstance.class) {
        if (instance == null) {
          instance = new DoubleCheckedInstance();
        }
      }
    }

    return instance;
  }
  
}

这里的 instance = new DoubleCheckedInstance();,看起来 Java 代码只有一行,应该是无法就行重排序的,实际上其编译后的实际指令是如下三步:

  1. 分配对象的内存空间
  2. 初始化对象
  3. 设置 instance 指向刚刚已经分配的内存地址

上面的第 2 步和第 3 步如果改变执行顺序也不会改变单线程的执行结果,也就是说可能会发生重排序,下图是一种多线程并发执行的场景:

此时线程 B 获取到的 instance 是没有初始化过的,如果此来访问 instance 的成员变量就可能触发空指针异常。导致有序性问题的本质原因是编译器优化。那你可能会想既然缓存和编译器优化是导致可见性问题和有序性问题的原因,那直接禁用掉不就可以彻底解决这些问题了吗,但是如果这么做了的话,程序的性能可能就会受到比较大的影响了。

其实可以换一种思路,能不能把这些禁用缓存和编译器优化的权利交给编码的工程师来处理,他们肯定最清楚什么时候需要禁用,这样就只需要提供按需禁用缓存和编译优化的方法即可,使用比较灵活。因此Java 内存模型就诞生了,它规范了 JVM 如何提供按需禁用缓存和编译优化的方法,规定了 JVM 必须遵守一组最小的保证,这个最小保证规定了线程对共享变量的写入操作何时对其它线程可见。

三、顺序一致性内存模型

顺序一致性模型是一个理想化后的理论参考模型,处理器和编程语言的内存模型的设计都是参考的顺序一致性模型理论。其有如下两大特性:

  1. 一个线程中的所有操作必须按照程序的顺序来执行
  2. 所有的线程都只能看到一个单一的执行操作顺序,不管程序是否同步

在工程师视角下的顺序一致性模型如下:

顺序一致性模型有一个单一的全局内存,这个全局内存可以通过左右摇摆的开关可以连接到任意一个线程,每个线程都必须按照程序的顺序来执行内存的读和写操作。该理想模型下,任务时刻都只能有一个线程可以连接到内存,当多个线程并发执行时,就可以通过开关就可以把多个线程的读和写操作串行化

顺序一致性模型中,所有操操作完全按照顺序串行执行,但是在 JMM 中就没有这个保证了,未同步的程序在 JMM 中不仅程序的执行顺序是无序的,而且由于本地内存的存在,所有线程看到的操作顺序也可能会不一致,比如一个线程把写共享变量保存在本地内存中,在还没有刷新到主内存前,其它线程是不可见的,只有更新到主内存后,其它线程才有可能看到。

JMM 对在正确同步的程序做了顺序一致性的保证,也就是程序的执行结果和该程序在顺序一致性内存模型中的执行结果相同。

四、Happens-Before 规则

Happens-Before 规则是 JMM 中的核心概念,Happens-Before 概念最开始在 这篇论文 提出,其在论文中使用 Happens-Before 来定义分布式系统之间的偏序关系。在 jsR-133 中使用 Happens-Before 来指定两个操作之间的执行顺序。

JMM 正是通过这个规则来保证跨线程的内存可见性,Happens-Before 的含义是前面一个对共享变量的操作结果对该变量的后续操作是可见的,约束了编译器的优化行为,虽然允许编译器优化,但是优化后的代码必须要满足 Happens-Before 规则,这个规则给工程师做了这个保证:同步的多线程程序是按照 Happens-Before 指定的顺序来执行的。目的就是为了在不改变程序(单线程或者正确同步的多线程程序)执行结果的前提下,尽最大可能的提高程序执行的效率。

JSR-133 规范中定了如下 6 项 Happens-Before 规则:

  1. 程序顺序规则:一个线程中的每个操作,Happens-Before 该线程中的任意后续操作
  2. 监视器锁规则:对一个锁的解锁操作,Happens-Before 于后面对这个锁的加锁操作
  3. volatile 规则:对一个 volatile 类型的变量的写操作,Happens-Before 与任意后面对这个 volatile 变量的读操作
  4. 传递性规则:如果操作 A Happens-Before 于操作 B,并且操作 B Happens-Before 于操作 C,则操作 A Happens-Before 于操作 C
  5. start() 规则:如果一个线程 A 执行操作 threadB.start() 启动线程 B,那么线程 A 的 start() 操作 Happens-Before 于线程 B 的任意操作
  6. join() 规则:如果线程 A 执行操作 threadB.join() 并成功返回,那么线程 B 中的任意操作 Happens-Before 于线程 A 从 threadB.join() 操作成功返回

JMM 的一个基本原则是:只要不改变单线程和正确同步的多线程的执行结果,编译器和处理器随便怎么优化都可以,实际上对于应用开发人员对于两个操作是否真的被重排序并不关心,真正关心的是执行结果不能被修改。因此 Happens-Before 本质上和 sa-if-serial 的语义是一致的,只是 sa-if-serial 只是保证在单线程下的执行结果不被改变。

总结:
本文主要介绍了内存模型的相关基础知识和相关概念,JMM 屏蔽了不同处理器内存模型之间的差异,在不同的处理器平台上给应用开发人员抽象出了统一的 Java 内存模型(JMM)。常见的处理器内存模型比 JMM 的要弱,因此 JVM 会在生成字节码指令时在适当的位置插入内存屏障(内存屏障的类型会因处理器平台而有所不同)来限制部分重排序。更多关于Java 内存模型的资料请关注编程网其它相关文章!,希望大家以后多多支持编程网!

--结束END--

本文标题: Java 内存模型(JVM)

本文链接: https://www.lsjlt.com/news/133495.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Java 内存模型(JVM)
    目录前言一、什么是 Java 内存模型二、为什么需要 Java 内存模型三、顺序一致性内存模型四、Happens-Before 规则前言 在并发编程中,当多个线程同时访问同一个共享...
    99+
    2022-11-12
  • java jvm内存模型有哪些
    Java虚拟机(JVM)内存模型主要有以下几个部分:1. 堆内存(Heap):用于存储Java对象的实例以及数组。堆内存是所有线程共...
    99+
    2023-10-12
    java jvm
  • 【JVM】JVM内存模型详解
    一、JVM是什么? JVM是Java Virtual Machine(Java虚拟机)的缩写,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。由一套字节码指令集、一组寄存器、一个栈、一个垃圾回收...
    99+
    2023-09-16
    jvm java 面试
  • 【JVM】JVM内存模型(详细)
    目录 一.JVM概述1.jvm简介2.jvm作用3.jvm的内存模型 二.类加载器1.类加载器的作用2.加载器的类型3.双亲委派机制的运行过程4.双亲委派机制优缺点5.为什么要破坏双亲委派机制6.破坏双亲委派机制的方式 三....
    99+
    2023-08-16
    jvm java 面试
  • 什么是JVM内存模型?
    本篇文章带大家初步了解一下JVM内存模型,有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。计算机内存模型在程序运行时,CPU通过访问主存获取数据,但随着CPU的快速发展,CPU访问速度越来越高,硬件无法满足CPU的条件下,大...
    99+
    2016-08-29
    java教程 java JVM
  • jvm中java内存模型的示例分析
    这篇文章主要介绍了jvm中java内存模型的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。一、java内存模型和java内存结构有什么区别 1、java内存...
    99+
    2023-06-19
  • java内存模型jvm虚拟机简要分析
    目录主内存和工作内存内存间的交互操作原子性、可见性、有序性原子性可见性有序性主内存和工作内存 Java 内存模型规定了所有的变量都存储在主内存中, 每条线程有自己的工作内存...
    99+
    2022-11-12
  • JVM(Java虚拟机)详解(JVM 内存模型、堆、GC、直接内存、性能调优)
    JVM(Java虚拟机) JVM 内存模型 结构图 jdk1.8 结构图(极简) jdk1.8 结构图(简单) JVM(Java虚拟机): 是一个抽象的计算模型。如同一台真实的机器,它有自己的指令集和执行引擎,可以在运行时操控内存区域。...
    99+
    2023-08-30
    jvm GC 直接内存 jvm性能调优
  • java jvm内存模型的操作方法有哪些
    Java虚拟机(JVM)的内存模型操作方法主要有以下几种:1. 堆内存管理:Java堆是JVM管理的最大的一块内存区域,用于存储对象...
    99+
    2023-10-18
    java jvm
  • Java常见知识点中Jvm内存结构、Java内存模型、Java对象模型的区别是什么
    这篇文章将为大家详细讲解有关Java常见知识点中Jvm内存结构、Java内存模型、Java对象模型的区别是什么,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。我们都知道,Java代码是要运行在...
    99+
    2023-06-05
  • 详解JVM系列之内存模型
    目录1. 内存模型和运行时数据区2、思维导图和图例3、对象向JVM申请空间4、为什么需要Survivor区?5、为什么需要两个Survivor区?6、例子进行验证堆内...
    99+
    2022-11-12
  • JVM内存模型/内存空间:运行时数据区
    目录JVM内存模型/内存空间① 程序计数器 (Program Counter Register)② Java虚拟机栈 (VM Stack)&n...
    99+
    2022-11-12
  • 如何进行JVM内存模型使用
    这篇文章给大家介绍如何进行JVM内存模型使用,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。你对JVM内存模型是否熟悉,这里和大家分享一下,主要包括栈和堆两部分内容,Java栈是与每一个线程关联的,JVM在创建每一个线程...
    99+
    2023-06-17
  • JVM 运行时数据区与JMM 内存模型
    目录1. JVM 运行时数据区2. JMM 内存模型硬件内存模型JMM3. 可见行与 volatile 关键字1. JVM 运行时数据区 JVM运行时数据区可以分为元空间,堆,虚拟机...
    99+
    2022-11-13
    JVM 运行时数据区 JMM 内存模型
  • 一篇文章带你了解JVM内存模型
    目录1. JVM介绍 1.1 什么是JVM?1.2 JVM的优点 1.2.1 一次编写,到处运行。1.2.2 自动内存管理,垃圾回收机制。1.2.3 数组下标越界...
    99+
    2022-11-12
  • JVM系列之内存模型的示例分析
    这篇文章主要介绍JVM系列之内存模型的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!1. 内存模型和运行时数据区这一章学习java虚拟机内存模型(Java Virtual machine menory mod...
    99+
    2023-06-15
  • Java内存模型详解
    目录什么是JMM主存与工作内存volatile 关键字有什么用一个线程对共享变量做了修改之后,其他的线程能够看到(感知到)该变量的这种修改(变化)什么是JMM JMM全称Java M...
    99+
    2023-05-18
    Java内存模型 Java JMM模型
  • Java——内存模型详解!
    Java内存模型是一种抽象的规则或规范,定义了程序中存在竞争现象的对象(包括实例字段、静态字段和数组对象,不包括局部变量,形式参数;后者是线程私有,不存在竞争问题)的访问方式。         如果我们要想深入了解Java并发编程,就要先理...
    99+
    2023-10-20
    java 开发语言
  • Java内存模型final的内存语义
    目录1、final域的重排序规则final2、写final域的重排序规则3、读final与的重排序规则4、final域为引用类型5、为什么final引用不能从构造函数内“逸出”6、f...
    99+
    2022-11-12
  • Java内存区域与内存模型详解
    这篇文章主要介绍“Java内存区域与内存模型详解”,在日常操作中,相信很多人在Java内存区域与内存模型详解问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Java内存区域与内存模型详解”的疑惑有所帮助!接下来...
    99+
    2023-06-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作