Loading [MathJax]/jax/input/TeX/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >一文搞定JMM核心原理

一文搞定JMM核心原理

作者头像
鲁大猿
发布于 2024-01-14 12:37:08
发布于 2024-01-14 12:37:08
15900
代码可运行
举报
文章被收录于专栏:架构技术专栏架构技术专栏
运行总次数:0
代码可运行

JMM引入

从堆栈说起

JVM内部使用的Java内存模型在线程栈和堆之间划分内存。此图从逻辑角度说明了Java内存模型:

# 堆栈里面放了什么?

线程堆栈还包含正在执行的每个方法的所有局部变量(调用堆栈上的所有方法)。线程只能访问它自己的线程堆栈。由线程创建的局部变量对于创建它的线程以外的所有其他线程是不可见的。即使两个线程正在执行完全相同的代码,两个线程仍将在每个自己的线程堆栈中创建该代码的局部变量。因此,每个线程都有自己的每个局部变量的版本。

基本类型的所有局部变量(boolean,byte,short,char,int,long,float,double)完全存储在线程堆栈中,因此对其他线程不可见。一个线程可以将一个基本类型变量的副本传递给另一个线程,但它不能共享原始局部变量本身。

堆包含了在Java应用程序中创建的所有对象,无论创建该对象的线程是什么。这包括基本类型的包装类(例如Byte,Integer,Long等)。无论是创建对象并将其分配给局部变量,还是创建为另一个对象的成员变量,该对象仍然存储在堆上。

局部变量可以是基本类型,在这种情况下,它完全保留在线程堆栈上。

局部变量也可以是对象的引用。在这种情况下,引用(局部变量)存储在线程堆栈中,但是对象本身存储在堆(Heap)上。

对象的成员变量与对象本身一起存储在堆上。当成员变量是基本类型时,以及它是对象的引用时都是如此。

静态类变量也与类定义一起存储在堆上。

线程栈如何访问堆上对象?

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

两个线程有一组局部变量。其中一个局部变量(局部变量2)指向堆上的共享对象(对象3)。两个线程各自对同一对象具有不同的引用。它们的引用是局部变量,因此存储在每个线程的线程堆栈中(在每个线程堆栈上)。但是,这两个不同的引用指向堆上的同一个对象。

注意共享对象(对象3)如何将对象2和对象4作为成员变量引用(由对象3到对象2和对象4的箭头所示)。通过对象3中的这些成员变量引用,两个线程可以访问对象2和对象4.

该图还显示了一个局部变量,该变量指向堆上的两个不同对象。在这种情况下,引用指向两个不同的对象(对象1和对象5),而不是同一个对象。理论上,如果两个线程都引用了两个对象,则两个线程都可以访问对象1和对象5。但是在上图中,每个线程只引用了两个对象中的一个。

线程栈访问堆示例

那么,什么样的Java代码可以导致上面的内存图? 好吧,代码就像下面的代码一样简单:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class MyRunnable implements Runnable() {

    public void run() {
        methodOne();
    }

    public void methodOne() {
        int localVariable1 = 45;

        MySharedObject localVariable2 =
            MySharedObject.sharedInstance;

        //... do more with local variables.

        methodTwo();
    }

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

        //... do more with local variable.
    }
}

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 member1 = 67890;
}

如果两个线程正在执行run()方法,则前面显示的图表将是结果。run()方法调用methodOne(),methodOne()调用methodTwo()。

methodOne()声明一个局部基本类型变量(类型为int的localVariable1)和一个局部变量,它是一个对象引用(localVariable2)。

执行methodOne()的每个线程将在各自的线程堆栈上创建自己的localVariable1和localVariable2副本。localVariable1变量将完全相互分离,只存在于每个线程的线程堆栈中。一个线程无法看到另一个线程对其localVariable1副本所做的更改。

执行methodOne()的每个线程也将创建自己的localVariable2副本。但是,localVariable2的两个不同副本最终都指向堆上的同一个对象。代码将localVariable2设置为指向静态变量引用的对象。静态变量只有一个副本,此副本存储在堆上。因此,localVariable2的两个副本最终都指向静态变量指向的MySharedObject的同一个实例。MySharedObject实例也存储在堆上。它对应于上图中的对象3。

注意MySharedObject类还包含两个成员变量。成员变量本身与对象一起存储在堆上。两个成员变量指向另外两个Integer对象。这些Integer对象对应于上图中的Object 2和Object 4。

另请注意methodTwo()如何创建名为localVariable1的局部变量。此局部变量是对Integer对象的对象引用。该方法将localVariable1引用设置为指向新的Integer实例。localVariable1引用将存储在执行methodTwo()的每个线程的一个副本中。实例化的两个Integer对象将存储在堆上,但由于该方法每次执行该方法时都会创建一个新的Integer对象,因此执行此方法的两个线程将创建单独的Integer实例。在methodTwo()中创建的Integer对象对应于上图中的Object 1和Object 5。

另请注意类型为long的MySharedObject类中的两个成员变量,它们是基本类型。由于这些变量是成员变量,因此它们仍与对象一起存储在堆上。只有局部变量存储在线程堆栈中。

JMM与硬件内存结构关系

硬件内存结构简介

现代硬件内存架构与内部Java内存模型略有不同。了解硬件内存架构也很重要,以了解Java内存模型如何与其一起工作。本节介绍了常见的硬件内存架构,后面的部分将介绍Java内存模型如何与其配合使用。

这是现代计算机硬件架构的简化图:

现代计算机通常有2个或更多CPU。其中一些CPU也可能有多个内核。关键是,在具有2个或更多CPU的现代计算机上,可以同时运行多个线程。每个CPU都能够在任何给定时间运行一个线程。这意味着如果您的Java应用程序是多线程的,线程真的在可能同时运行.

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

每个CPU还可以具有CPU高速缓存存储器层。事实上,大多数现代CPU都有一些大小的缓存存储层。CPU可以比主存储器更快地访问其高速缓存存储器,但通常不会像访问其内部寄存器那样快。因此,CPU高速缓存存储器介于内部寄存器和主存储器的速度之间。某些CPU可能有多个缓存层(级别1和级别2),但要了解Java内存模型如何与内存交互,这一点并不重要。重要的是要知道CPU可以有某种缓存存储层。

计算机还包含主存储区(RAM)。所有CPU都可以访问主内存。主存储区通常比CPU的高速缓存存储器大得多。同时访问速度也就较慢.

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

JMM与硬件内存连接 - 引入

如前所述,Java内存模型和硬件内存架构是不同的。硬件内存架构不区分线程堆栈和堆。在硬件上,线程堆栈和堆都位于主存储器中。线程堆栈和堆的一部分有时可能存在于CPU高速缓存和内部CPU寄存器中。这在图中说明:

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

  • Visibility of thread updates (writes) to shared variables.
  • Race conditions when reading, checking and writing shared variables. 以下各节将解释这两个问题。

JMM与硬件内存连接 - 对象共享后的可见性

如果两个或多个线程共享一个对象,而没有正确使用volatile声明或同步,则一个线程对共享对象的更新可能对其他线程不可见。

想象一下,共享对象最初存储在主存储器中。然后,在CPU上运行的线程将共享对象读入其CPU缓存中。它在那里对共享对象进行了更改。只要CPU缓存尚未刷新回主内存,共享对象的更改版本对于在其他CPU上运行的线程是不可见的。这样,每个线程最终都可能拥有自己的共享对象副本,每个副本都位于不同的CPU缓存中。

下图描绘了该情况。在左CPU上运行的一个线程将共享对象复制到其CPU缓存中,并将其count变量更改为2.对于在右边的CPU上运行的其他线程,此更改不可见,因为计数更新尚未刷新回主内存中.

要解决此问题,您可以使用Java的volatile关键字。volatile关键字可以确保直接从主内存读取给定变量,并在更新时始终写回主内存。

JMM与硬件内存连接 - 竞态条件

如果两个或多个线程共享一个对象,并且多个线程更新该共享对象中的变量,则可能会出现竞态。

想象一下,如果线程A将共享对象的变量计数读入其CPU缓存中。想象一下,线程B也做同样的事情,但是进入不同的CPU缓存。现在,线程A将一个添加到count,而线程B执行相同的操作。现在var1已经增加了两次,每个CPU缓存一次。

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

但是,两个增量同时执行而没有适当的同步。无论线程A和B中哪一个将其更新后的计数版本写回主存储器,更新的值将仅比原始值高1,尽管有两个增量。

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

要解决此问题,您可以使用Java synchronized块。同步块保证在任何给定时间只有一个线程可以进入代码的给定关键部分。同步块还保证在同步块内访问的所有变量都将从主存储器中读入,当线程退出同步块时,所有更新的变量将再次刷新回主存储器,无论变量是不是声明为volatile

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-01-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 架构技术专栏 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
java内存模型介绍[通俗易懂]
####Java内存模型 Java内存模型描述了Java虚拟机和计算机内存之间是如何协同工作的。一个Java虚拟机也是一个完整的计算机的模型,因此,这个模型自然也包含了内存模型。
全栈程序员站长
2022/09/08
2790
java内存模型介绍[通俗易懂]
Java内存模型深度解读
Java内存模型深度解读 Java内存模型规范了Java虚拟机与计算机内存是如何协同工作的。Java虚拟机是一个完整的计算机的一个模型,因此这个模型自然也包含一个内存模型——又称为Java内存模型。 如果你想设计表现良好的并发程序,理解Java内存模型是非常重要的。Java内存模型规定了如何和何时可以看到由其他线程修改过后的共享变量的值,以及在必须时如何同步的访问共享变量。 原始的Java内存模型存在一些不足,因此Java内存模型在Java1.5时被重新修订。这个版本的Java内存模型在Java8中人在使用
用户1289394
2018/02/27
7300
Java内存模型深度解读
深度学习Java之内存模型【译】
Java的内存模型定义了Java虚拟机如何和计算机物理内存进行交互。Java虚拟机是一体化的计算机模型,所以它自然也包含了内存模型。
老钱
2018/08/15
3350
一文详解JMM(Java 内存模型)
要想要理解透彻JMM(Java内存模型),首先我们要从CPU缓存模型和指令重排序讲起!
会呼吸的Coder
2022/12/02
1K0
一文详解JMM(Java 内存模型)
Java内存模型
Java内存模型(简称JMM)指定了JVM如何利用计算机内存(RAM)进行工作。JMM与整个计算机的模型类似,这个模型自然也包含内存模型,即Java内存模型(AKA)。
码代码的陈同学
2018/07/25
9860
Java内存模型
并发编程2:认识并发编程的利与弊
本文探讨了Java并发编程中容易让人误解的地方,包括内存模型、线程、锁、死锁等内容。作者通过引用Java官方文档和一些社区优秀文章的讲解,总结了一系列Java并发编程中需要注意的点,对于初学者或者对并发编程了解不够深入的开发者具有一定的帮助。
张拭心 shixinzhang
2018/01/05
7990
并发编程2:认识并发编程的利与弊
Java并发编程(1)-并发基础
下图简单的展示了最简单的高速缓存的配置,数据的读取和存储都经过高速缓存,CPU核心与高速缓存有一条特殊的快速通道;主存与高速缓存都连在系统总线上(BUS)这条总线同时还用于其他组件的通信:
端碗吹水
2020/09/21
6040
Java并发编程(1)-并发基础
Java并发性和多线程
例如一个应用程序需要从本地文件系统中读取和处理文件的情景. 比方说, 从磁盘读取一个文件需要5s, 处理一个文件需要2s. 那么处理两个文件就需要:
烟草的香味
2019/10/24
7750
面试官:可以说下Java内存模型(JMM)吗
Java内存模型规范规定了一个线程如何和何时可以看到其他由线程修改过的共享变量的值,以及在必须时如何同步的访问共享变量。Java 内存模型(Java Memory Model,JMM)就是一种符合内存模型规范的,屏蔽了各种硬件和操作系统的访问差异的,保证了 Java 程序在各种平台下对内存的访问都能保证效果一致的机制及规范。
共饮一杯无
2022/11/28
6820
面试官:可以说下Java内存模型(JMM)吗
Java多线程-带你认识Java内存模型,内存分区,从原理剖析Volatile关键字
地址:https://juejin.im/post/59f8231a5188252946503294
用户2802329
2018/08/07
4550
Java多线程-带你认识Java内存模型,内存分区,从原理剖析Volatile关键字
面试系列之-JMM内存模型(JAVA基础)
JMM定义了一组规则或规范,该规范定义了一个线程对共享变量写入时,如何确保对另一个线程是可见的。实际上,JMM提供了合理的禁用缓存以及禁止重排序的方法,所以其核心的价值在于解决可见性和有序性。
用户4283147
2023/09/11
3000
面试系列之-JMM内存模型(JAVA基础)
吊打Java面试官-Java内存模型深入详解(JMM)
为了提高程序运行的性能,现代CPU在很多方面对程序进行了优化。: 例如: CPU高速缓存。 尽可能地避免处理器访问主内存的时间开销,处理器大多会利用缓 存(cache)以提高性能。
JavaEdge
2020/05/27
4730
吊打Java面试官-Java内存模型深入详解(JMM)
一篇文章弄懂Java多线程基础和Java内存模型
写在前面:提起多线程大部门同学可能都会皱起眉头不知道多线程到底是什么、什么时候可以用到、用的时候是不是有共享变量问题等等一大堆问题。本篇文章将分为两部分第一部分是讲解多线程基础、第二部分讲解Java内存模型。
全栈程序员站长
2022/08/31
2550
一篇文章弄懂Java多线程基础和Java内存模型
走进高并发(三)深入理解Java内存模型
在讨论Java内存模型之前,这里先一起聊聊CPU、高速缓存以及主内存,在了解这些知识后,对理解Java内存模型会有很大的帮助。
itlemon
2020/06/23
4100
谁说深入浅出虚拟机难?现在我让他通俗易懂(JVM)
1:什么是JVM 大家可以想想,JVM 是什么?JVM是用来干什么的?在这里我列出了三个概念,第一个是JVM,第二个是JDK,第三个是JRE。相信大家对这三个不会很陌生,相信你们都用过,但是,你们对这三个概念有清晰的知道么?我不知道你们会不会,知不知道。接下来你们看看我对JVM的理解。
吴生
2018/04/18
9970
谁说深入浅出虚拟机难?现在我让他通俗易懂(JVM)
深度解析Java多线程的内存模型内部java内存模型硬件层面的内存模型Java内存模型和硬件内存模型的联系小结
Java内存模型很好的说明了JVM是如何在内存里工作的,JVM可以理解为java执行的一个操作系统,作为一个操作系统就有内存模型,这就是我们常说的JAVA内存模型。
desperate633
2018/08/22
5650
深度解析Java多线程的内存模型内部java内存模型硬件层面的内存模型Java内存模型和硬件内存模型的联系小结
进阶课程1:jvm内存模型
堆内存中包含了 Java 代码中创建的所有对象,不管是哪个线程创建的。 其中也涵盖了包装类型(例如 Byte,Integer, Long 等)。
你可以叫我老白
2023/06/25
2280
进阶课程1:jvm内存模型
终于有人把Java内存模型说清楚了
JVM 中试图定义一种 JMM 来屏蔽各种硬件和操作系统的内存访问差异,以实现让 Java 程序在各种平台下都能达到一致的内存访问效果。
李红
2019/05/31
4820
Android Handler机制1之Thread
每一个进程都有自己的独立的一块内存空间、一组资源系统。其内部数据和状态都是完全独立的。进程的优点是提高CPU的运行效率,在同一个时间内执行多个程序,即并发执行。但是从严格上将,也不是绝对的同一时刻执行多个程序,只不过CPU在执行时通过时间片等调度算法不同进程告诉切换。
隔壁老李头
2018/08/30
8200
Android Handler机制1之Thread
JMM和底层实现原理
线程的通信是指线程之间以何种机制来交换信息。在编程中,线程之间的通信机制有两种,共享内存和消息传递。 在共享内存的并发模型里,线程之间共享程序的公共状态,线程之间通过写-读内存中的公共状态来隐式进行通信,典型的共享内存通信方式就是通过共享对象进行通信。 在消息传递的并发模型里,线程之间没有公共状态,线程之间必须通过明确的发送消息来显式进行通信,在java中典型的消息传递方式就是wait()和notify()。
allsmallpig
2021/02/25
9200
相关推荐
java内存模型介绍[通俗易懂]
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验