JMM(Java Memory Model)-java内存模型(一)

写在前面

在看这篇博客之前,请先把JVM运行时内存模型忘掉。因为两者不是一个维度的划分。本篇进队JMM做一个基本的介绍,基于一些文章和资料,写一下自己对于这一概念的理解。后续会根据多线程来做详解。如有误,请指正。
文中没有涉及对计算机乱序执行优化部分的介绍,放在后面研究后再做总结。

概述

在讲JMM之前,先看一下java虚拟机和计算机如何进行交互。
JMM(Java Memory Model)-java内存模型(一)_第1张图片
由上图可以看到java虚拟机并没有跟计算机硬件进行直接交互,而是跟操作系统机型交互来在计算机上运行程序。
所以在开始介绍JMM之前我们要先了解一下计算机的内存模型和操作系统。

1、计算机组成

计算机主要由两部分组成:

  • (1)硬件:硬件是计算机赖以工作的实体,包括显示器、键盘、鼠标、硬盘、CPU、主板等;
  • (2)软件:软件会按照用户的要求协调整台计算机的工作,比如 Windows、Linux、Mac OS、Android 等操作系统,以及 Office、QQ、迅雷、微信等应用程序。
    大家都知道,计算机的主要数学计算和逻辑计算都在CPU中进行。所以CPU的性能一定程度上决定了计算机性能,下面就来针对CPU来看一下其工作原理。

1.1. 计算机硬件- CPU

1.1.1 CPU工作原理

CPU 的根本任务就是执行指令,对计算机来说最终都是一串由 0 和 1 组成的序列。CPU 从逻辑上可以划分成 3 个模块:

  • (1)控制器:对整个CPU进行调度,发送指令,指令的规范就是CPU的指令集,不同的CPU有不同的指令集。CPU指令集有(主流)ARM 和 X86两类。CPU指令集取决于CPU的体系架构
  • (2)运算器
    在控制信号(控制器发出的指令)的作用下可完成加、减、乘、除四则运算和各种逻辑运算。
  • (3)寄存器
    CPU的运算速度是非常快的,为了性能CPU在内部开辟一小块临时存储区域,并在进行运算时先将数据从内存复制到这一小块临时存储区域中,运算时就在这一小快临时存储区域内进行。我们称这一小块临时存储区域为寄存器。

1.1.2 高速缓存,缓存一致性问题

随着多CPU技术的升级,其运算能力也大幅度提升。但与之对应的内存读写性能就不能满足其调用频率了。所以引入了高速缓存(Cache)来作为内存与处理器之间的缓冲,简单来说高速缓存就是寄存器的表亲。是对寄存器的逻辑上的拓展。根据CPU型号的不同也对应不同的高速缓存,有的分两级,有的分三级。
工作原理:将运算需要使用到的数据复制到缓存中,让运算能快速进行,当运算结束后再从缓存同步回内存之中,这样处理器就无须等待缓慢的内存读写了。
这就带来了新的问题。缓存一致性问题。即多处理器系统中,每个处理器都有一个高速缓存区。加入这些高速缓存区中有一部分内容对应同一块内存中的数据。那么在告诉缓存同步数据到内存时,该同步谁的?

1.2 计算机内存模型

为了保证缓存的一致性,不同的CPU提供了不同的缓存一致性协议(如:MSI、MESI、MOSI等等),要求CPU在对数据进行读写时遵循一定的规范。其在计算机工作中起到的作用如下:
JMM(Java Memory Model)-java内存模型(一)_第2张图片

1.3、计算机软件-操作系统

操作系统(Operating System,OS)是软件的一部分,它是硬件基础上的第一层软件,是硬件和其它软件沟通的桥梁(或者说接口、中间人、中介等)。

1.3.1 操作系统工作原理

简单来说操作系统就是通过调用各种硬件的指令来驱动硬件,并且对外提供接口。
操作系统主要分两个部分:内核及对外提供的系统调动用层。

1.3.1.1 内核

内核程序主要管理硬件,起着计算机系统资源分配的功能。
只要硬件不同(如X86架构和RISC架构的CPU),内核就要进行修改才行。内核主要具有如下功能。

  • (1)系统调用接口(system call interface)
    可以方便程序员轻易的与内核的通信,将硬件的资源进一步利用
    -(2)程序管理(调度器)
    主要工作是给CPU分配资源,良好的CPU调度机制将会有效加快整体系统性能。
  • (3).内存管理(Memory management)
    控制整个系统的内存管理,内存控制是非常重要的,因为系统所有的程序代码与数据都必须要先存放在内存当中。通常内核会提供虚拟内存功能。如java程序必须转化为二进制文件通过类加载器加载到内存中。
  • (4)文件系统管理
    数据的输入/输出等的工作,还有不同文件格式的支持。
  • (5)设备驱动
    硬件的管理是内核主要工作之一,设备的驱动程序就是内核需要做的事情。每个设备都有自己的指令集,操作系统通过指令集驱动设备

1.3.1.2 系统调动用层

内核的工作是进行计算机的资源分配,所以上面需要有应用程序,才能真正赋予计算机生命。
对于应用程序来说,操作系统提供了一整组开发接口,来帮助运行在该操作系统上的应用程序跟计算机做交互,这就是系统调用层
软件工程师只要遵循公认的系统调用参数来开发软件,该软件就能在系统上面运行。
这也就是为什么我们说java程序是跟操作系统进行交互,并不接触硬件的原因

1.3.2 常用操作系统

常用的操作系统有LINUX和 WINDOWS等。
LINUX:支持多种指令集,各种CPU架构都可以跑
WINDOWS: WINDOWS目前只支持X86指令集, WINDOWS RT只支持ARM指令集;
因此我们在线上的程序部署通常选用LINUX操作系统。

2、Java Memory Model(java内存模型)

了解完java应用程序如何和计算机交互及计算机组成和数据结构后。我们再来看一下java内存模型。

  • 首先最重要的一点:java内存模型是基于计算机内存模型的一种抽象版本,且并不直接对应计算机内存模型中的组件。 如果强行对应的话,工作内存主要对应CPU中的寄存器和高速缓存部分,主内存对应硬盘内存部分。
  • 也有人试图将其和java虚拟机运行时内存模型对应起来,不建议做对应。两者不是一个概念上的东西,JMM其目试图屏蔽掉各种硬件和操作系统的内存访问差异,是为了更好的跟计算机进行交互;而java虚拟机运行时内存模型主要为了管理内存。
    比如两台机器,由于硬件和操作系统不一样,内存模型就存在差异,在并发访问时,两台机器的表现可能就不一样。如果采用了统一规范,内存模型一直的情况下就不会出现这种差异,这也就是JMM存在的意义。

2.1 主内存和工作内存

java内存模型规定了所有的变量都存在主内存(Main Memory)中,所有的线程都有自己的工作内存。
并且提供了8中基本操作进行主内存和工作内存进行交互。且针对这8中基本操作时必须满足的规则。其工作模式如下:
JMM(Java Memory Model)-java内存模型(一)_第3张图片

  • 8种基本操作

    方法 含义
    lock(锁定) 作用于主内存的变量,它把一个变量标识为一条线程独占的状态
    unlock(解锁) 作用于主内存的变量,把锁定中状态的变量,释放出来。释放出来后,其他线程才可以访问
    read(读取) 作用于主内存的变量,把变量从主内存传输到线程的工作内存,给后续的load动作使用
    load(载入) 作用于工作内存的变量,它把read从主内存中读取的变量值放入工作内存的变量副本中
    use(使用) 作用于工作内存的变量,它把工作内存中的变量值传递给执行引擎,每当虚拟机遇到一个需要使用该变量值的字节码指令时,执行这步操作
    assign(赋值) 作用于工作内存的变量,它把一个从执行引擎中接受到的值赋给工作内存中的变量,每当虚拟机遇到一个需要给变量赋值的字节码指令都会执行这步操作
    store(存储) 作用于工作内存的变量,它把工作内存中的变量值传送给主内存中,一遍后面write写入
    write(写入) 作用于主内存的变量,它把store操作从工作内存中得到的变量值放入主内存的变量中
  • 操作规则

    序号 规则
    1 不允许read和load、store和write操作之一单独出现(即不允许一个变量从主存读取了但是工作内存不接受,或者从工作内存发起会写了但是主存不接受的情况),以上两个操作必须按顺序执行,但没有保证必须连续执行,也就是说,read与load之间、store与write之间是可插入其他指令的。
    2 不允许一个线程丢弃它的最近的assign操作,即变量在工作内存中改变了之后必须把该变化同步回主内存。
    3 不允许一个线程无原因地(没有发生过任何assign操作)把数据从线程的工作内存同步回主内存中。
    4 一个新的变量只能从主内存中“诞生”,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量,换句话说就是对一个变量实施use和store操作之前,必须先执行过了assign和load操作。
    5 一个变量在同一个时刻只允许一条线程对其执行lock操作,但lock操作可以被同一个条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁。
    6 如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行load或assign操作初始化变量的值
    7 如果一个变量实现没有被lock操作锁定,则不允许对它执行unlock操作,也不允许去unlock一个被其他线程锁定的变量
    8 对一个变量执行unlock操作之前,必须先把此变量同步回主内存(执行store和write操作)

本文进队JMM的概念和原理做一个解释,具体的会通过多线程的介绍来进行详细介绍。

2.2 java线程

由上图可以看到,JMM中工作内存是对应java线程的,那么了解JMM工作原理之前,我们再来看一下java线程相关的知识。首先我们先来了解3个概念:

  • 内核线程(Kernel-Level Thread KLT):操作系统内核支持的线程,这种线程由内核完成切换,内核通过操作调度器进行线程调度。
    并负责将线程的任务映射到各个处理器上。每个内核线程可以认为是内核的一个分身,这样操作系统就有能力处理多个任务,支持多线程的内核就叫做多线程内核(Multi-Thread kernel);
  • 轻量级进程(Light Weight Process,LWP):内核线程的一种高级接口,和内核线程是1:1的关系
  • 用户线程(User Thread ,UT):一个线程只要不是内核线程都是用户线程。但轻量级进程是由内核实现,许多操作都要进行系统调用,效率受到限制。所以一般我们理解的用户线程指完全建立在用户空间的线程上,系统内核完全不能感知。

然后再来看虚拟机线程的3中实现方式:

  • 1、使用轻量级内核线程实现(一对一模型):轻量级内核线程与内核线程之间的关系称为一对一线程模型。
  • 2、使用用户线程实现,(一对多模型):用户线程和进程是一对多的关系
  • 3、使用用户线程加轻量级线程(N对M模型):用户线程和轻量级线程是N对M的关系。

java线程在JDK1.2版本后由原来的用户线程模型改为基于操作系统原生线程模型来实现。所以在windows和Linux中都是基于一对一线程模型实现的,而Solaris操作系统支持一对多和一对一两种模型。

2.1.1 java线程的调度方式

  • 协同式:线程把自己的工作执行完后,主动通知系统切换到另一线程。
    • 缺点:线程时间不可控,容易阻塞
  • 抢占式:每个线程由系统分配执行时间,线程切换由系统控制。原则上这种方式可以设置线程优先级,但java线程和操作系统线程直接关联,执行顺序取决于操作系统,所以没什么用。
    • 优点:执行时间可控,不会线程阻塞

2.1.2 java线程状态

java语言定义了5中线程状态,在任意一个时间点,一个线程有且只有其中一种状态。
JMM(Java Memory Model)-java内存模型(一)_第4张图片

  • 新建(new):创建后尚未启动的线程
  • 运行(Runnable):包括了操作系统线程状态的running和ready,也就是说此状态的线程有可能正在执行,也有可能正在等待CUP分配资源
  • 无限期等待(waiting):处于这种状态的线程不会被CPU分配资源它们需要等待被其他线程显示的唤醒,以下方法会让线程陷入无限期的等待:
    • 没有设置Timeout参数的Object.wait()方法;
    • 没有设置Timeout参数的Thread.join()方法;
    • LockSupport.park()方法;
  • 限期等待(Timed Waiting):处于这种状态的线程不会被CPU分配资源,无需等待其他线程唤醒,在设定的时间后会由系统自动唤醒。以下方法会让线程进入限期等待:
    • Thread.sleep()方法
      • 设置了Timeout参数的Object.wait()方法;
    • 设置了Timeout参数的Thread.join()方法;
    • LockSupport.parkNonos()方法;
    • LockSupport.parkUtil()方法;
  • 阻塞(Blocked):阻塞是等待着获取一个排它锁,等待是等待被唤醒或时间;
  • 结束(Terminated):线程已终止

2.1.2 线程之间的通讯方式

java线程之间有两种通讯方式:共享内存和消息传递。
共享内存
线程之间共享程序状态,线程之间通写、读内存中的公共状态来进行隐式通信,最典型的例子就是volatile、synchronized、和加锁;
消息传递
线程之间没有公共状态,线程之间必须通过明确的发送消息来进行显示的通信,在Java中最典型显示消息传递就是wait()、notify()、notifyAll()、sleepping()方法。

你可能感兴趣的:(JVM,java)