我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Java内存模型的深入讲解

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Java内存模型的深入讲解

Java内存模型展示了Java虚拟机是如何与计算机内存交互的,解决多线程读写共享内存时资源访问的问题。

内存模型

Java虚拟机中的内存模型将线程栈与堆划分开,下图描述了Java内存模型的逻辑图。

每个线程都要自己的线程栈,栈中存储着线程执行到当前位置所调用的方法信息,线程执行代码时,线程栈会不断执行入栈和出栈操作。

线程栈中会存储所有被调用的方法中定义的变量,并且自己访问自己栈中的变量,别的线程不可见。即使两个线程执行相同的代码,也会在线程自己的栈中重复创建变量。一个线程可能会传递变量副本给另一个线程,但不能共享变量本身。

在栈中变量存储形式也有所不同。属于基本变量类型(int,byte,long,boolean,char,double,float,short)的变量,会直接将变量值存储在栈中,而其余类型的变量的值被存储在堆中,线程栈中只保留指向堆中变量地址的指针。
堆中则存储Java程序中创建的所有对象,不管是什么线程创建的。创建对象并将其分配给局部变量,或者将其创建为另一个对象的成员变量都没有影响,该对象仍存储在堆中。

值得注意的是,Java中的静态类变量也会随着类初始化而存储在堆中。

有指向对象指针的所有线程都可以访问堆上的对象。当线程可以访问对象时,它也可以访问该对象的成员变量。如果两个线程同时在同一个对象上调用一个方法,则它们都将有权访问该对象的成员变量,但是每个线程将拥有自己的局部变量副本。

两个线程有一组局部变量,指向堆上的共享对象。这两个线程分别具有对同一对象的不同指针。它们的指针也是局部变量,因此存储在每个线程的线程栈中(在每个线程上)。但是,两个不同的指针指向堆上的同一对象。
下面的代码块就是上图的一个实际例子。


public class MyRunnable implements Runnable() {

    public void run() {
        methodOne();
    }

    public void methodOne() {
        int localVariable1 = 45;
        MySharedObject localVariable2 = MySharedObject.sharedInstance;
        //...
        methodTwo();
    }

    public void methodTwo() {
        Integer localVariable1 = new Integer(99);
        //...
    }
}

public class MySharedObject {

    //static variable pointing to instance of MySharedObject
    public static final MySharedObject sharedInstance = new MySharedObject();

    //member variables pointing to two objects on the heap
    public Integer object2 = new Integer(22);
    public Integer object4 = new Integer(44);

    public long member1 = 12345;
    public long member2 = 67890;
}

硬件架构

现代硬件的内存架构与Java内存模型还是有些不同的,了解硬件架构对理解Java内存模型也有帮助。简单的硬件架构图如下:

现代计算机一般是多核CPU,一般不止一个CPU,因此多个线程是可能在物理意义上并发运行的。这意味着,如果Java应用程序是多线程的,则每个CPU可能在Java应用程序中同时(并发)运行一个线程。

每个CPU包含一组寄存器,这些寄存器本质上是CPU内存储器。CPU在这些寄存器上执行操作的速度比对主存储器中的变量执行操作的速度快得多,这是因为CPU可以比访问主存储器更快地访问这些寄存器。

每个CPU可能还具有一个CPU高速缓存。实际上,大多数现代CPU都有一定大小的高速缓存。CPU可以比其主存储器更快地访问其高速缓存,但是通常不如其访问其内部寄存器的速度快。因此,CPU高速缓存存储器位于内部寄存器和主存储器之间的速度之间。某些CPU可能具有多个高速缓存层(L1和L2 Cache)。了解Java内存模型如何与内存交互并不是很重要,重要的是要知道CPU可以具有某种高速缓存层。

计算机还包含一个主存储区(RAM)。所有CPU都可以访问主存储器。主存储区通常比CPU的高速缓存大得多。

通常,当CPU需要访问主内存时,它将部分主内存读入其CPU缓存中。它甚至可以将缓存的一部分读入其内部寄存器,然后对其执行操作。当CPU需要将结果写回主存储器时,它将把值从其内部寄存器刷新到高速缓存,然后在某个时候将值刷新回主存储器。

当CPU需要将其他内容存储在高速缓存中时,通常会将高速缓存中存储的值刷新回主存储器。CPU高速缓存可以一次将数据写入其部分内存,并一次刷新其部分内存。它不必每次更新都读取/写入完整的缓存。通常,缓存在称为“缓存行”的较小存储块中更新,可以将一个或多个高速缓存行读入高速缓存存储器,并且可以将一个或多个高速缓存行再次刷新回主存储器。

Java内存模型与硬件关联

如前所述,Java内存模型和硬件内存体系结构是不同的,硬件内存体系结构不能区分线程堆栈和堆。在硬件上,线程堆栈和堆都位于主内存中。线程堆栈和堆的某些部分有时可能会出现在CPU缓存和内部CPU寄存器中。下图对此进行了说明:

当对象和变量可以存储在计算机的各种不同存储区域中时,可能会出现某些问题。 两个主要问题是:

  • 线程更新(写入)到共享变量的可见性。
  • 读取,检查和写入共享变量时的竞争条件。

对象的可见性

如果两个或多个线程共享一个对象,而没有正确使用volatile关键字,则一个线程对共享对象进行的更新可能对其他线程不可见。

每个线程都可以拥有自己的共享库副本,每个副本位于不同的CPU缓存中。想象一下,共享对象最初存储在主存储器中。然后,在CPU上运行的一个线程将共享对象读入其CPU缓存并进行修改。只要未将CPU缓存刷新回主存储器,在其他CPU上运行的线程就看不到共享对象的更改版本。

下图说明了这种情况,在左CPU上运行的一个线程将共享对象复制到其CPU缓存中,并将其count变量更改为2。在右CPU上运行的其他线程看不到此更改,因为尚未将count更新写回主内存。

当然这个问题可以使用volatile关键字来解决。

竞争条件

如果两个或多个线程共享一个对象,并且一个以上的线程更新该共享对象中的变量,则可能会发生竞争条件。

假如线程A将共享对象的变量count读入其CPU缓存中,而线程B执行同样操作,但是它位于不同的CPU缓存中。现在,线程A加一个要计数,线程B也执行相同的操作。现在count已增加两次,在每个CPU高速缓存中增加一次。

如果这些增加是顺序执行的,则变量计数将增加两次,并将原始值+2写回到主存储器中。

但是,这两个增量是在没有同步的情况下并发执行的。不管线程A和B中哪个线程将其更新后的版本写回主内存,尽管有两个增量,但更新后的值仅比原始值高1。

该图说明了如上所述的竞争条件问题的发生:

这个问题可以使用synchronized关键字来解决。

总结

到此这篇关于Java内存模型的文章就介绍到这了,更多相关Java内存模型内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Java内存模型的深入讲解

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

下载Word文档

猜你喜欢

深入了解volatile和Java内存模型

在本篇文章当中,主要给大家深入介绍Volatile关键字和Java内存模型。在文章当中首先先介绍volatile的作用和Java内存模型,然后层层递进介绍实现这些的具体原理、JVM底层是如何实现volatile的和JVM实现的汇编代码以及CPU内部结构,感兴趣的可以了解一下
2022-11-13

Java内存模型的深入分析

曾经,计算机的世界远没有现在复杂,那时候的cpu只有单核,我们写的程序也只会在单核上按代码顺序依次执行,根本不用考虑太多。
Java内存2024-12-03

怎么深入理解Java内存模型JMM

这期内容当中小编将会给大家带来有关怎么深入理解Java内存模型JMM,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。Java 内存模型Java 内存模型(JMM)是一种抽象的概念,并不真实存在,它描述了一组
2023-06-05

深入理解JVM内存模型

JVM内存结构包括方法区、堆、栈、本地方法栈和程序计数器。不同的内存区域有不同的作用和管理方式,合理地使用和管理内存是编写高效、稳定的Java程序的重要方面。
JVM内存2024-11-30

从 CPU 说起,深入理解 Java 内存模型!

这篇文章我们从底层 CPU 开始讲起,一直讲到操作系统,最后讲到了编程语言层面,让大家能够一环扣一环地理解,最后明白 Java 内存模型诞生的原因(上层有数据一致性问题),以及最终要解决的问题(缓存一致性问题)。

Java 内存模型进阶:深入理解 happens-before 关系

Java 内存模型(JMM)是 Java 虚拟机(JVM)内存管理和访问的规范。happens-before 关系是 JMM 的核心概念之一,它规定了线程之间内存操作的顺序,对于线程安全和并发编程至关重要。本文将深入探讨 happens-before 关系,从基本概念到高级应用,提供全面的理解。
Java 内存模型进阶:深入理解 happens-before 关系
2024-02-04

深入理解Java内存模型(JMM)及Volatile关键字

本篇我们继续来学习JMM模型以及Volatile关键字的那些面试必问的一些知识点。
Java2024-12-03

GoLang内存模型详细讲解

go官方介绍go内存模型的时候说:探究在什么条件下,goroutine在读取一个变量的值的时,能够看到其它goroutine对这个变量进行的写的结果,Go内存模型规定了一些条件,在这些条件下,在一个goroutine中读取变量返回的值能够确保是另一个goroutine中对该变量写入的值
2022-12-15

一文带你深入理解JVM内存模型

在共享内存的并发模型里面,线程之间共享程序的公共状态,线程之间通过读写内存中公共状态来进行隐式通信。
JVM内存模型2024-12-14

Java内存模型详解

JMM全称JavaMemoryModel,中文翻译Java内存模型,一种符合内存模型规范的,屏蔽了各种硬件和操作系统的访问差异的,本详细介绍了Java内存模型,感兴趣的同学可以参考一下
2023-05-18

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录