006 - 理解Java内存模型

本篇文章参考自全面理解Java内存模型(JMM)及volatile关键字,更多细节可以查看原博文,本文主要是做一个简单的总结

1. Java内存模型概述

  • Java内存模型(即Java Memory Model,简称JMM)

    ​ 本身是一种抽象的概念,它描述的是一组规则或规范

    ​ 主要目的是 "解决Java并发编程中多线程通过共享内存进行通信时,存在的原子性、可见性(缓存一致性)以及有序性问题 ”

    ​ 除此之外,Java内存模型还提供了一系列原语,封装了底层实现后,供开发者直接使用。如我们常用的一些关键字:synchronized、volatile以及并发包等

  • 具体来说

    • JVM运行程序的实体是线程,每个线程创建时,JVM会为线程创建一个工作内存 ,此空间是该线程私有,对其他线程不可见
    • JMM规定所有的共享变量存储在主内存中,所有线程可以访问
    • 但是线程对上边变量访问需要在工作内存中,需要将主内存中数据copy到工作内存,即工作内存中数据是主内存中数据一个备份(或者叫副本)
    • 线程在自己的工作内存中,对上边数据处理完,再刷新回主内存
  • 用图表示,如下

006 - 理解Java内存模型_第1张图片

  • 主内存

    • 主要**存储 Java实例对象,所有线程创建的实例对象都存放在主内存中,不管该实例对象是成员变量还是方法中的本地变量(也称局部变量)**
  • 工作内存

    • 主要存储当前方法本地变量信息,对其他线程不可见,也不存在线程安全问题
    • 即使两个线程走的是同一段代码,也会在各自的工作内存中分别创建属于自己的本地变量
  • 主内存与工作内存的数据存储类型以及操作方式

    对于实例对象的成员方法 变量是基本数据类型 直接存储在栈中
    变量时引用数据类型 对象保存在栈中,对象实例存储在堆中,实例对象的成员变量,不管是基本数据类型、引用数据类型,都会存放在堆区
    static类型 存储在方法区,线程共享

006 - 理解Java内存模型_第2张图片

2. JVM内存区域

  • JMM与Java内存区域的划分是不同的概念层次,简单的对比一下

    • JMM描述的是一组规则,通过这组规则控制程序中各个变量在共享数据区域和私有数据区域的访问方式,JMM是围绕原子性,有序性、可见性展开的
    • java内存区域是JVM在运行时,对管理的的内存划分的几块区域
  • Java内存区域如下图所示

    [外链图片转存失败(img-SXc6wqmf-1567493644690)(…/img/6.png)]

  • 方法区 (Method Area)

    属于线程共享的内存区域, 又称(Non-Heap)非堆,主要存储被虚拟机加载的类信息、常量、静态变量,编译后的代码等数据。

    方法区无法满足内存分配,会抛出OutOfMemoryError。

    方法区中存在一个运行时常量池,主要用于存放编译器生成的各种字面量和符号引用

  • 堆 (Heap)

    堆也是线程共享的内存区域。主要存放对象实例,堆是虚拟机启动时创建,是垃圾收集器管理的主要区域

    如果没有内存 完成实例分配,并且堆无法扩展时,抛出OutOfMemoryError异常

  • 程序计数器(Program Counter Register):

    线程私有 , 小块内存空间,主要代表当前线程所执行的字节码行号指示器。分支、循环、跳转、异常处理、线程恢复等基础功能都以来此计数器完成

  • 虚拟机栈(Java Virtual Machine Stacks)

    线程私有,其他线程无法保存该栈内存中的数据,与线程同时创建。代表Java方法执行的内存模型。

    每个方法执行时都会创建一个栈帧来存储方法的变量表、操作数栈、动态链接方法、返回值、返回信息等

    每个方法的调用 和 结束对于一个栈帧的入栈和出栈

    对于对象,只保存引用,具体对象实例保存在堆中

  • 本地方法栈(Native Method Stacks)

    线程私有,和本地方法有关

2.1 JVM中线程共享和线程独占的区域

JVM中,有两块内存区域可以被所有线程共享

  • 堆,上边存放所有对象
  • 方法区,上面存放静态变量

线程独占的

  • 线程栈

3. 硬件结构和JMM

  • 硬件结构 : CPU、缓存、内存的数据交互

    现在计算机一班拥有多个CPU,并且每个CPU拥有多个核心。多核心指的是一枚处理器(CPU)拥有多个完整的计算引擎(内核),保证多任务的执行。

    从多线程的角度思考,每个线程会映射到CPU的每个核心中运行

    CPU中存在一组寄存器,存放CPU可以直接访问和操作数据,一般CPU都是从内存中读取数据到寄存器处理

    因为CPU处理速度远远大于内存,因此需要在内存和CPU之间添加 CPU缓存,但是不能保证CPU每次都可以从缓存中获取数据 , 如果获取不到,CPU还需要去内存中取数据,这里是缓存的命中率

006 - 理解Java内存模型_第3张图片

  • 硬件处理器 线程模型

    • Java多线程实际是调用系统内核线程模型

      ​ 在Window系统和Linux系统上,Java线程的实现是基于一对一模型,是通过语言级别层面程序去间接调用系统内核的线程模型.即我们在使用Java线程,java虚拟机实际上调用当前操作系统的内核线程来完成当前任务

    • 内核线程 (Kernel-Level Thread,KLT)

      ​ 它是由操作系统内核(kernel)支持的线程 , 这种线程由操作系统内核来完成线程切换, 内核通过调度器对线程进行调度,并将任务映射到各个处理器,每个内核线程可以视为内核的一个分身

    • 轻量级进程(Light Weight Process)

      ​ 也就是通常意义上的线程,由于每个轻量级进程都会映射到一个内核线程,因此我们可以通过轻量级的进程调用内核线程,进而由操作系统内核将任务映射到各个处理器。

      ​ 这种轻量级线程与内核线程一对一关系,如下图

006 - 理解Java内存模型_第4张图片

  • JMM与硬件内存架构的关系

    对于硬件内存来说,只有寄存器、缓存内存、主内存的概念,并没有工作内存,主内存之分。

    而JMM只是一个抽象的概念,是一组规则,并不实际存在,不管是工作内存的数据还是主内存的数据都会存储在计算机主内存中,当然也有可能存储到CPU缓存或者寄存器中,因此它们关系更像是下图显示,相互交叉的关系
    006 - 理解Java内存模型_第5张图片

4. JMM存在的必要性

  • 决定一个线程对共享变量的写入何时对另一个线程可见

    JVM运行程序的实体是线程,每个线程创建时JVM会为线程创建一个线程栈(工作内存),用来存储线程私有的数据。

    线程与主内存的变量操作必须通过工作内存来完成,主要过程是将变量从主内存copy到线程自己的工作内存,线程在自己的栈空间内对变量进行操作,操作完成之后将变量写回到主内存

    为了保证线程操作的变量对另一个线程可见,需要引入JMM规范

你可能感兴趣的:(Java并发编程)