JVM——CPU缓存架构与Java 内存模型

导航

  • 一、CPU缓存架构与一致性协议
    • 1.1 CPU缓存架构
    • 1.2 缓存行与伪共享问题
    • 1.3 MESI 缓存一致性协议
    • 1.4 伪共享的解决办法
  • 二、JMM Java 内存模型
    • 2.1 JMM 简介
    • 2.2 原子性、可见性、有序性
    • 2.3 八大内存交互操作
    • 2.4 happens-before 原则

一、CPU缓存架构与一致性协议

1.1 CPU缓存架构

现代 CPU 的发展非常快,内存的速度已经完全跟不上。如果将CPU完成一个基本操作所用的时间定义为时钟周期,那么 CPU 的指令处理速度要比内存的加载速度快100 倍左右。

为了解决这个性能上的鸿沟,现代 CPU 架构往往采用如下图所示的缓存架构:
在这里插入图片描述
在多核CPU和主存(Main Memory)之间引入三级高速缓存——L1、L2、L3

越靠近CPU的缓存,成本造价越高、性能越强、存储空间越小,其中 L3 缓存是多核共享,而L1、L2 是核内私有。

1.2 缓存行与伪共享问题

缓存行 Cache Line 是高速缓存一次读取内存数据的最小单位。目前最常见的 Intel cpu 的缓存行大小是 64 Bytes。
连续的数据很有可能由于数据跨度恰好在一个缓存行内,就很有可能会被CPU加载到 L1、L2、L3 高速缓存中。

由于高速缓存的存在,引出了缓存一致性协议,它可以保证 L1、L2、L3 中的数据在多核CPU和并发场景下不会出现线程不一致问题。早期的解决方案并不是各种缓存一致性方案,而是采用总线锁的方式。

总线锁,顾名思义就是将高速缓存与主内存之间的总线上锁,只允许一个线程去获取并操作上锁数据,处理完成后释放锁,并将数据从缓存中刷回主存。这种方式虽然安全,但也牺牲了很大的性能。于是,人们又引入了诸如 MESI 的缓存一致性协议。简单的说,就是给缓存数据做标记,如果CPU发现缓存的数据失效了,就必须从主存中重新加载最新的数据。

那什么又是伪共享呢?

伪共享是缓存行加载数据时必然会存在的性能损耗问题。当对象中包含线程局部变量,且尺寸大小小于 64 字节,就有可能发生伪共享问题。

再具体点,内存地址连续的不同变量被加载到了同一个缓存行中,而同一个缓存行中的多个变量,又不一定是CPU所需的,这种情况就是伪共享。

由于伪共享的存在,CPU核心中的高速缓存加载了本不需要数据,又在“缓存一致性协议”的要求下,不得不在这些无用数据失效后重新加载缓存,导致缓存失效,或造成性能损耗:
在这里插入图片描述
如上图所示,x、y 两个变量由 Main Memory 加载到 core 1 和 core 2两个核心的 L1、L2 缓存中。如果线程A、B 跑在两个核心上,且线程 A 修改 core 1 中的 x,由于缓存一致性协议, core 2 中的 x 变量将会失效,它必须从主内存中重新加载,这样频繁的加载、访问主内存, core 2 中的 L1、L2 缓存几乎等于失效。

1.3 MESI 缓存一致性协议

MESI是缓存锁的实现方式之一,注意这是 CPU 缓存一致性,并非通常说的应用缓存。有些无法被缓存的数据或跨多个缓存行的数据依然必须使用总线锁。

MESI 的核心思想是为每个 Cache Line 标记 4 种状态:
在这里插入图片描述

  1. 若缓存数据更改过,则将 Cache Line 标记为 M
  2. 如果缓存数据是独享,则标记为 E
  3. 如果数据是被多个CPU读取,则标记为 S
  4. 如果数据被其他CPU修改过,则标记为 I

1.4 伪共享的解决办法

Java 7 之前可以采用字节填充的方式,例如针对不同操作系统和对象头的大小,补齐多个 long 类型的空数据。
但这种方式在 Java 7 的某个版本中会出现 填充失效问题,原因是该版本的虚拟机优化了未使用 field 的排布。

在 Java 8 加入 @Contended 注解会帮助增加128 字节的 padding,并且需要开启 -XX:-RestrictContended 选项才能生效。

虽然解决了伪共享的问题,但是这种填充的方式也浪费了缓存资源,而且缓存又小又贵,时间和空间的取舍要酌情考虑。

二、JMM Java 内存模型

2.1 JMM 简介

JMM 全称是 “Java Memory Model”,Java 内存模型。
因为在不同的硬件生产商和操作系统下,内存的访问方式各有所差异,这样就会造成相同的代码出现不一样的问题,而 JMM 屏蔽掉了各种操作系统的内存访问差异,以实现“Write Once,Run Anywhere”的目标

JMM 中规定所有的变量都存储在主内存 (Main Mem)中,包括实例变量、静态变量,但是不包括局部变量和方法参数。每条线程都有自己的工作内存(Work Mem),线程私有。工作内存中保存的是线程的变量从主内存中的拷贝副本

这种结构的基本工作方式是:线程对变量的读和写都必须在工作内存中进行,而线程之间变量值的传递均需要通过主内存来完成。如下图所示,注意与Java 内存结构(堆栈等)等概念区分开。

在这里插入图片描述

2.2 原子性、可见性、有序性

整个 JMM 实际上是围绕着三个并发特征建立起来的——原子性、可见性、有序性

  1. 原子性:和事务的 ACID 的 原子性概念一致,即表示一个操作中间不可分割,不能中断,执行过程不允许被其他线程打扰。 JMM 只能保证基本操作的原子性,如果要保证一个代码块的原子性,Java 提供了 synchronized 关键字,它对应了 monitorentrer 和 monitorexit 字节码指令。
  2. 可见性:不同的线程对数据的修改结果可以被其他线程感知到,这就是可见性。synchronized 是保证可见性的最常用的操作,除此之外,还有 volatile 关键字,它是较弱的同步机制。
  3. 有序性:字节码指令的执行顺序不可重排。可以使用 synchronized 或 volatile 保证多线程之间操作的有序性。volatile是使用内存屏障达到禁止指令重排,保证有序性。而 synchronized 则以互斥锁的形式要求上锁的资源必须按序执行。

2.3 八大内存交互操作

在这里插入图片描述
说是 8 种内存交互操作:

  • lock 和 unlock:锁定与解锁。作用于主内存的变量,将其设置为线程独占或解除。
  • Read 和 Write:发生在主内存和工作内存之间,将变量传输到工作内存,将从工作内存得到的值放入主内存中。
  • Load 和 Store:作用于工作内存的变量,将工作内存中的变量放入副本中,将工作内存中的变量传输到主内存中。
  • Use 和 Assign:将工作内存中的变量传输到执行引擎,将一个从执行引擎中接收到的值赋值给工作内存的副本。

使用规则:

  • 不允许 read、load、store、write 操作之一单独出现,即 read 操作后必须 load,store后必须 write。
  • 不允许线程丢弃它最近的 assign 操作,即工作内存中的变量修改之后,必须告知主存。
  • 不允许线程将没有 assign 的数据从工作内存同步到主存。
  • lock 和 unlock 必须成对出现。
  • 新的变量必须由主存中诞生。
  • 如果对一个变量进行 lock,会清空所有工作内存中此变量的值。在执行引擎使用这个变量前,必须重新 load 或 assign 。
  • unlock 之前,必须将此变量同步回主内存。

2.4 happens-before 原则

happens-before是JMM提供的有序性保证。在理解它的概念之前,需要了解一下弱内存模型强内存模型

弱内存模型——weak memory model
弱内存模型中,loadload、loadstore、storestore、storeload 四种内存重排序都有可能发生。只要不改变单线程行为,弱内存模型可随意对代码进行重排序,这是一种近乎不存在任何保证的模型。

强内存模型
Java的内存模型属于一种叫做 “顺序一致性” 的强内存模型。在顺序一致性模型中,不再存在重排序,Java的JMM就属于这种强内存模型。

虽然 JMM 属于最高级别的强内存模型——顺序一致性内存模型,但并不是说Java 不允许重排序,而是在某些情况下,可以支持严格限制重排序的目的,如volatile。而弱内存模型是无法满足这种保证的。

happens-before 是 JMM 提供的一系列关于重排序问题的规则。可以概括为:

If one action happens-before another, then the first is visible to and ordered before the second.
如果一个动作 happens-before 另一个动作,那么前者的执行会排在后者的前面,并且(其结果)对后者可见。
《java language specification 8》

可以分为以下两种策略:

  1. 对于会改变程序执行结果的重排序,JMM要求编译器和处理器必须禁止这种重排序。
  2. 对于不会改变程序执行结果的重排序,JMM对编译器和处理器不做要求(JMM允许这种重排序)。

这两个策略概括起来,可以这样理解——只要不改变程序的执行结果,编译器和处理器想怎么优化就怎么优化

例如,如果编译器经过细致的分析,认定一个锁只能被一个线程访问到,那么这个锁可以被消除。
又例如,编译器认定一个 volatile 变量只会被单个线程访问到,那么它可以把这个 volatile 变量当做一个普通变量对待。

happens-before 的具体规则如下:

  1. 程序顺序执行,如果x和y是同一个线程的动作,并且在程序顺序中x出现在y之前,则 x happens-before y。
  2. 如果动作 x 的结果需要同步给后面的动作 y ,那么 x happens-before y。也就是说,synchronized 修饰的情况,先获得锁的线程就是 x ,后获得锁的就是 y,前者的执行结果需要同步给 y,那么同样会有 x happens-before y。
  3. 传递性,x happens-before y,y happens-before z,那么 x happens-before z。

根据这些规则,以下这些场景都是 happens-before 的情况:

  1. 对象锁的解锁,发生在对象锁后续的上锁之前
  2. 对 volatile 变量的写操作,一定发生在后续对该 volatile 变量的读操作之前。
  3. 对线程的 start() 方法的调用,一定发生在线程内其他操作之前。
  4. 线程中的所有操作,一定发生在该线程 join() 方法结束之前。
  5. 任何对象的默认初始化一定发生在程序中其他操作之前。

总之,happens-before 定义了两个动作发生资源竞争时的时间顺序,是JMM 描述特定动作前后执行顺序的一种抽象关系。它原则上不允许重排序,但在一些不影响程序执行结果的情况下,乱序执行也是可以的。

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

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

相关文章

白噪声检测_科学家尝试用智能扬声器的白噪声来监测婴儿的呼吸运动

华盛顿大学的一支研究团队,刚刚介绍了他们开发的一种新型智能扬声器技术。这种设备能够借助白噪声来安抚熟睡的婴儿,并监测他们的呼吸和运动。具体说来是,通过智能扬声器发出的白噪声,原型设备能够将之与生命体征监测仪的数据相匹…

最大值_285期 博最大值2路,已经箭在弦上!

往期数据P-5掉码 跨度 和尾 012断路 余数和 位数86072 1 8 4 200 断1路2 5 对214对 双双双79703 0 2 3 101 断2路2 4 对215对 单单单62386 0 4 1 020 断1路2 4 错216对 双双单71903 0 8 7 110 断2路2 5 错217对 单单单64838 0 4 8 012 来3路3 4 错218对 双双双02052 0 2 2 020 …

商品领域ddd_为 Gopher 打造 DDD 系列:领域模型-资源库

前言: 作为领域模型中最重要的环节之一的Repository,其通过对外暴露接口屏蔽了内部的复杂性,又有其隐式写时复制的巧妙代码设计,完美的将DDD中的Repository的概念与代码相结合!Repository资源库通常标识一个存储的区域…

mysql5.7主从全备恢复_Mysql5.7—运维常用备份方式(超全)

小生博客:http://xsboke.blog.51cto.com小生 Q Q:1770058260-------谢谢您的参考,如有疑问,欢迎交流一、 Mysqldump备份结合binlog日志恢复使用mysqldump进行全库备份,并使用binlog日志备份,还原时&#xf…

docker 运行容器_Docker之运行 Django 容器

首先此篇笔记默认你已经安装好了 Docker,并了解 Docker 的基础概念,诸如镜像、容器、以及他们之间的关系等。如果不太了解,等我回头了解清楚以后,可以再写一篇文章阐述一下。(狗头当然,对于这篇文章&#x…

mysql8.0与mysql7.0_MySQL 5.7 vs 8.0,哪个性能更牛?

测试mysql5.7和mysql8.0分别在读写,选定,只写模式下不同并发时的性能(tps,qps)最早测试使用版本为mysql5.7.22和mysql8.0.15sysbench测试前先重启mysql服务,并清除os的缓存(避免多次测试时命中缓存)每次进行测试都是新生成测试数据…

springmvc使用requestmapping无法访问控制类_研究人员称人类使用的新烟碱类杀虫剂让蜜蜂无法入睡...

来自布里斯托尔大学的科学家进行了研究,显示常见的杀虫剂可以阻止蜜蜂和苍蝇睡个好觉。就像人类一样,许多昆虫也需要睡眠才能正常工作。然而,如果它们接触过新烟碱类杀虫剂,它们的睡眠就会受到影响,新烟碱类杀虫剂是一…

linux 监控mysql脚本_Linux系统MySQL主从同步监控shell脚本

操作系统:CentOS系统目的:定时监控MySQL数据库主从是否同步,如果不同步,记录故障时间,并执行命令使主从恢复同步状态1、创建脚本文件vi /home/crontab/check_mysql_slave.sh #编辑,添加下面代码#!/bin/sh…

python协成_Python协程(上)

几个概念:event_loop 事件循环:程序开启一个无限的循环,程序员会把一些函数注册到事件循环上。当满足事件发生的时候,调用相应的协程函数。coroutine 协程:协程对象,指一个使用async关键字定义的函数&#…

js父元素获取子元素img_css,前端_父标签div中包含一个子元素img标签,子元素div标签,为什么img要加上浮动,子元素div才会处于正常位置?,css,前端 - phpStudy...

父标签div中包含一个子元素img标签,子元素div标签,为什么img要加上浮动,子元素div才会处于正常位置?dom结构如图img加上float 子元素div显示正常。不加float div显示错位。附上我写的一个dome测试用的,大家可本地看下究…

android运营商获取本机号码_一键登录已成大势所趋,Android端操作指南来啦!

根据极光(Aurora Mobile)发布的《2019年Q2移动互联网行业数据研究报告》,2019年第二季度,移动网民人均安装APP总量已达56款。面对如此繁多的APP,想在用户的手机中占据一席之地,移动开发者们就不得不努力提升用户体验。而现实却是&…

spring批量写入mysql数据库_MyBatis-spring和spring JDBC批量插入Mysql的效率比较

工具框架用spring-batch,数据库是mysql(未做特殊优化)。比较数据框架mybatis和spring jdbc的插入效率。Mybatis三种实现:1、mybatis的官方写法Java代码publicvoidbatchInsert1(List poilist)throwsException {SqlSession sqlSession sqlSessionFactory.…

金额转换java_java金额转换

像商品价格,订单,结算都会涉及到一些金额的问题,为了避免精度丢失通常会做一些处理,常规的系统中金额一般精确到小数点后两位,也就是分;这样数据库在设计的时候金额就直接存储整型数据类型,前端…

java bloomfilter_爬虫技术之——bloom filter(含java代码)

在爬虫系统中,在内存中维护着两个关于URL的队列,ToDo队列和Visited队列,ToDo队列存放的是爬虫从已经爬取的网页中解析出来的即将爬取的URL,但是网页是互联的,很可能解析出来的URL是已经爬取到的,因此需要VI…

java php js_【javascript/PHP】当一个JavaScripter初次进入PHP的世界,他将看到这样的风景...

本文将从以下11点介绍javascript和PHP在基础语法和基本操作上的异同:1.数据类型的异同2.常量和变量的定义的不同,字符串连接运算符不同3.对象的创建方法的不同4.PHP与JS在变量声明提升和函数声明提升的差异5.var在JS和PHP中使用的差异6.PHP和JS在访问对象…

从零开始学java 框架_从零开始学 Java - 搭建 Spring MVC 框架

如果创建一个 Spring 项目Spring MVC 框架在 Java 的 Web 项目中应该是无人不知的吧,你不会搭建一个 Spring 框架?作为身为一个刚刚学习Java的我都会,如果你不会的话,那可真令人忧伤。1.在 MyEclipse 创建项目后,可以以…

java 系统类型_Java获取操作系统类型

Java获取操作系统完整版系统枚举类:public enum EPlatform {Any("any"),Linux("Linux"),Mac_OS("Mac OS"),Mac_OS_X("Mac OS X"),Windows("Windows"),OS2("OS/2"),Solaris("Solaris"),SunOS…

azure mysql on vnet_管理 VNet 终结点 - Azure 门户 - Azure Database for MySQL | Microsoft Docs

您现在访问的是微软AZURE全球版技术文档网站,若需要访问由世纪互联运营的MICROSOFT AZURE中国区技术文档网站,请访问 https://docs.azure.cn.使用 Azure 门户创建和管理 Azure Database for MySQL VNet 服务终结点和 VNet 规则Create and manage Azure D…

java jmap jc_利用jmap命令查看JVM内存使用详情

介绍打印出某个java进程(使用pid)内存内的,所有‘对象’的情况(如:产生那些对象,及其数量)。它的用途是为了展示java进程的内存映射信息,或者堆内存详情。可以输出所有内存中对象的工具,甚至可以将VM 中的heap&#xf…

雅居乐万豪酒店java_“万豪,我心所属之地” | 上海雅居乐万豪酒店Terence Sun的实习故事...

​我叫孙庭骏,来自台北,目前就读瑞士恺撒里兹酒店管理学院,在来上海之前曾在瑞士苏黎世万豪酒店的餐饮部实习过两次,共一年的时间,所以万豪对我来说并不陌生。万豪一直深深吸引我的莫过于它的核心价值以及非凡待客之道…