一文搞定JMM核心原理

news2024/11/13 14:49:20

公众号《鲁大猿》,寻精品资料,帮你构建Java全栈知识体系 www.jiagoujishu.cn (架构技术.cn)

JMM引入

从堆栈说起

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

image.png

# 堆栈里面放了什么?

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

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

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

image.png

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

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

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

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

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

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

image.png

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

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

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

线程栈访问堆示例

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

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内存模型如何与其配合使用。

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

image.png

现代计算机通常有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寄存器中。 这在图中说明:

image.png

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

  • 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上运行的其他线程,此更改不可见,因为计数更新尚未刷新回主内存中.

image.png

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

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

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

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

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

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

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

image.png

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

本文由mdnice多平台发布

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1367296.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

鸿蒙开发解决agconnect sdk not initialized. please call initialize()

文章目录 项目场景:问题描述原因分析:解决方案:总结:项目场景: 鸿蒙开发报错: agconnect sdk not initialized. please call initialize() 问题描述 报错内容为: 10-25 11:41:01.152 6076-16676 E A0c0d0/JSApp: app Log: 数据查询失败: {“code”:1100001,“messag…

ArcGIS小技巧|四种计算图斑面积的方法

ArcGIS中有多种方法可计算出图斑面积,本文总结了四种方法,是否可堪称史上最全? 1、计算几何 这是最适合非专业人士的方法,直接利用ArcGIS中的计算几何功能进行计算。 a、首先添加一double类型字段,用来存储面积数值…

ip协议历史

今天的互联网,是万维网(WWW)一家独大。而在上世纪七八十年代,人们刚开始尝试网络连接时,那时出现了计算机科学研究网络、ALOHA 网、因时网、阿帕网等不同类型的网络,这些网络之间互相通信是个难题。 于是&…

YOLOv8改进 | 主干篇 | CSWinTransformer交叉形窗口网络

一、本文介绍 本文给大家带来的改进机制是CSWin Transformer,其基于Transformer架构,创新性地引入了交叉形窗口自注意力机制,用于有效地并行处理图像的水平和垂直条带,形成交叉形窗口以提高计算效率。它还提出了局部增强位置编码(LePE),更好地处理局部位置信息,我将其…

【Matplotlib】基础设置之图例处理05

标签 import numpy as np import matplotlib as mpl import matplotlib.pyplot as plt%matplotlib inlinelegend() 函数被用来添加图像的标签,其主要相关的属性有: legend entry - 一个 legend 包含一个或多个 entry,一个 entry 对应一个 k…

百亿储能企业「鹏辉能源」发来感谢信,携手企企通打造的采购供应链平台成功上线

近日,由企企通携手百亿储能上市企业【广州鹏辉能源科技股份有限公司】(以下简称“鹏辉能源”)打造的一站式数字化采购供应链管理平台成功上线。为感谢企企通在企业采购数字化升级过程中所做的贡献和努力,鹏辉能源向企企通团队发来…

腾讯云com域名注册1元条件说明

腾讯云com域名注册优惠价格1元首年,条件是企业新用户,个人新用户注册com域名是33元首年,第二年续费价格85元一年。活动 txybk.com/go/domain-sales 活动打开如下图: 腾讯云com域名注册优惠价格 腾讯云com域名注册原价是85元一年&a…

mysql最常见问题:允许远程访问和修改密码 的详细解决方法

目 录 一、不能通过navicat等数据库客户端远程访问 (一)问题 (二)解决方法:开启远程可访问 1、输入授权命令的方式 2、直接更改数据库中的用户表 二、修改数据库的密码 Mysq在Lin…

R语言(12):绘图

12.1 创建图形 12.1.1 plot函数 plot(c(1,2,3),c(1,2,4)) plot(c(1,2,3),c(1,2,4),"b") plot(c(-3,3),c(-1,5),"n",xlab "x",ylab "y")12.1.2 添加线条&#xff1a;abline()函数 x <- c(1,2,3) y <- c(1,3,8) plot(x,y) lm…

2023年生成式AI全球使用报告

生成式人工智能工具正在迅速改变多个领域&#xff0c;从营销和新闻到教育和艺术。 这些工具使用算法从大量培训材料中获取新的文本、音频或图像。虽然 ChatGPT 和 Midjourney 之类的工具可以用来实现超出人类能力或想象力的艺术效果&#xff0c;但目前它们最常用于比人类更轻松…

Codeforces Round 646 (Div. 2) C. Game On Leaves

题目链接&#xff1a;Problem - 1363C - Codeforces 题意&#xff1a;给定一颗树和一个节点x&#xff0c;每次从这棵树上删除一个叶子节点及其任何一条连接的边&#xff0c;Ayush先手&#xff0c;问谁先取到节点x。 博弈论问题&#xff0c;先看两个样例是如何取到的。 对于样例…

react / antd ProTable - 高级表格 合并行,子表头

ProTable - 高级表格 合并行&#xff0c;以及ProTable的用法 key React.key 确定这个列的唯一值&#xff0c;一般用于 dataIndex 重复的情况 dataIndex React.key | React.key[] 与实体映射的 key&#xff0c;数组会被转化 [a,b] > Entity.a.b valueType ProFieldValueType …

SSMBUG汇总

20240103 通用&#xff0c;驼峰命名法&#xff0c;mybatis。 mybatis入门程序中&#xff0c; // 获取对象的顺序为&#xff1a;SqlSessionFactoryBuild-》SqlSessionFactory-》SqlSessionSqlSessionFactoryBuilder sqlSessionFactoryBuilder new SqlSessionFactoryBuilder();I…

深入了解Spring框架

一、前言 Spring框架是一个广泛应用于企业级Java应用程序开发的轻量级、开源的框架。它提供了全面的基础设施支持&#xff0c;使开发者能够专注于业务逻辑的实现而不必过多关注底层的技术细节。本文将深入探讨Spring框架的实际应用和一些最佳实践&#xff0c;帮助开发者更好地利…

4030 【例题2】Cashier Employment 出纳员问题(Poj1275Hdu1529)————一本通(提高篇)

今天主要来讲讲差分约束 题目大意&#xff1a; 从0点到23点&#xff0c;给出每个时刻需要的售货员个数&#xff0c;再给出每个时刻应征的售货员个数&#xff0c;然后让你求出满足需求的最小售货员个数 解题思路&#xff1a;差分约束 #include <queue> #include <cs…

【QML COOK】- 002-添加一个图片

1. 编辑main.qml import QtQuickWindow {width: 800height: 800visible: truetitle: qsTr("Hello World")Image {anchors.fill: parentsource: "qrc:/Resources/Images/arrow.png"} }将Window的width和height都改成800&#xff0c;因为我们要添加的图片大…

SQL必知必会笔记(9~12章)

第九章 汇总数据 1、聚集函数用来进行记录数据的加工&#xff0c;然后再进行返回。 2、SQL的聚集函数&#xff1a; 函数 说明 AVG() 返回某列的平均值 COUNT() 返回某列的行数 MAX() 返回某列的最大值 MIN() 返回某列的最小值 SUM() 返回某列值之和 3、AVG()函数 A…

MySQL语法练习-DML语法练习

文章目录 0、相关文章1、添加数据2、修改数据3、删除数据4、总结 0、相关文章 《MySQL练习-DDL语法练习》 1、添加数据 # 给指定字段添加数据 insert into 表名 (字段名1,字段名2,...) values(值1,值2...);# 给全部字段添加数据 insert into 表名 values(值1,值2,...);#批量…

美易官方:美股2024年开局惨淡,调整会持续多久?

美股2024年开局惨淡&#xff0c;调整会持续多久&#xff1f;美易官方平台致力于为投资者与美股深度链接 2024年&#xff0c;美股市场迎来了一个艰难的开局。全球经济形势的不确定性、地缘政治紧张局势以及市场预期的波动都给美股市场带来了挑战。投资者们对于未来的走势充满了疑…

这一次技术学习分享,超过苦读30本书

同学们&#xff0c;做个问卷调查&#xff0c;你参加了这次由腾讯云主办的第四期“云梯计划”了不&#xff1f; “云梯计划”已连续举办三年&#xff0c;免费为超过1万名大学生提供了腾讯云认证培训和考试名额&#xff0c;帮助其提升就业竞争力。 想要得到免费的系统性、实战性…