zoukankan      html  css  js  c++  java
  • 【Java并发系列】--Java内存模型

    Java内存模型

    1 基本概念

    程序:代码,完成某一个任务的代码序列(静态概念)

    进程:程序在某些数据上的一次运行(动态)

    线程:一个进程有一个或多个线程组成(占有资源的独立单元)

    2 JVM与线程

    jvm启动时期

    类被调用: JVM线程启动——> 启动其他线程(main)

    3 jvm内存区域(运行时数据区)

    在这里插入图片描述

    Java虚拟机会在程序运行时将内存自动划分为以上几个区域,每个区域都有其作用及创建和销毁时机。

    • 方法区:(共享区域)存储已被加载的类信息、常量、静态变量(static)、即时编译器编译后的代码(JIT)等数据。根据Java 虚拟机规范的规定,当方法区无法满足内存分配需求时,将抛出OutOfMemoryError 异常。值得注意的是在方法区中存在一个叫运行时常量池(Runtime Constant Pool)的区域,它主要用于存放编译器生成的各种字面量和符号引用,这些内容将在类加载后存放到运行时常量池中,以便后续使用。

    • 堆区(Heap):(共享区域)存放类实例对象。GC主要管理区域,故有时也被称为:GC堆(OOM)如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError 异常。

    • 虚拟机栈区(VM stack):Java方法在运行时的内存模型,其大小固定可自行设定。(OOM:**该区域的内存溢出主要指整个VM stack的溢出,单个栈帧不会溢出,栈帧会根据方法执行需要的大小来申请栈帧空间 ** )

    在这里插入图片描述

    • 本地方法栈(Native Method Stack):本地方法栈属于线程私有的数据区域,这部分主要与虚拟机用到的 Native 方法相关,一般情况下,我们无需关心此区域。

    • 程序计数器(PC Register):Java线程私有数据,该数据就是执行下一条指令的地址。分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。

    4 Java内存模型 (Java memory model)JMM(规范,抽象的模型)

    在这里插入图片描述
    JMM不真实存在只是一种抽象概念,它描述了一种规则,用来规范程序中数据的访问方式。

    1)主内存:存放共享的信息(方法区和堆区中的数据)。

    2)工作空间:存放私有信息(虚拟机栈区,本地方法栈,程序计数器中的数据)。基本数据类型,直接分配到工作空间;引用类型:引用的地址存放在工作内存中,引用对象存放在堆中。实际数据在主存中。

    3)工作方式:

    • 1 线程修改私有数据:直接修改工作空间数据
    • 2 线程修改共享数据:数据先把主内存的数据复制到工作空间,然后在工作空间修改数据,修改完成后把工作空间的数据刷新到主内存中。

    5 硬件内存架构与Java内存模型

    1) 硬件架构
    在这里插入图片描述

    该架构描述了cpu与内存操作,在cpu工作时直接使用寄存器中的数据,因为寄存器存在于cpu的内部而且访问速度在内存设备中最快。但是由于寄存器小,不能存放大量数据。所以数据一般存放在内存中,但是内存的访问速度太慢,导致CPU在处理指令时往往花费很多时间在等待内存做准备工作 ,所以在寄存器和内存之间又加入了缓存,缓存比较小,但访问速度比主内存快得多 。如果CPU总是访问主内存中的同一址地的数据,很容易影响CPU执行速度,此时缓存就可以把从内存提取的数据暂时保存起来,如果寄存器要取内存中同一位置的数据,直接从缓存中提取,无需直接从主内存取。需要注意的是,寄存器并不每次数据都可以从缓存中取得数据,万一不是同一个内存地址中的数据,那寄存器还必须直接绕过缓存从内存中取数据。所以并不每次都得到缓存中取数据,这种现象有个专业的名称叫做缓存的命中率,从缓存中取就命中,不从缓存中取从内存中取,就没命中,可见缓存命中率的高低也会影响CPU执行性能,这就是CPU、缓存以及主内存间的简要交互过程,总而言之当一个CPU需要访问主存时,会先读取一部分主存数据到CPU缓存(当然如果CPU缓存中存在需要的数据就会直接从缓存获取),进而在读取CPU缓存到寄存器,当CPU需要写数据到主存时,同样会先刷新寄存器中的数据到CPU缓存,然后再把数据刷新到主内存中。

    • cpu缓存的一致性问题:并发处理不同步
    • 解决方案:
      • 1 总线加锁:
        • 缺点:降低cpu的吞吐量。不推荐该方案
      • 2 缓存一致性协议(MESI协议):
        • 当cpu在cache操作数据时,如果该数据是共享变量,数据在cache中读到寄存器中,再进行修改,并更新内存数据。
        • cache LINE置无效,其他的cpu不会从的cache中读数据,而是从内存中读数据

    2) Java线程与硬件处理器
    在这里插入图片描述

    ​ Java线程的实现是基于一对一的线程模型,所谓的一对一模型,实际上就是通过语言级别层面程序去间接调用系统内核的线程模型,即我们在使用Java线程时,Java虚拟机内部是转而调用当前操作系统的内核线程来完成当前任务 。内核线程(Kernel-Level Thread,KLT),它是由操作系统内核(Kernel)支持的线程,这种线程是由操作系统内核来完成线程切换,内核通过操作调度器进而对线程执行调度,并将线程的任务映射到各个处理器上。每个内核线程可以视为内核的一个分身,这也就是操作系统可以同时处理多任务的原因。

    3)Java内存模型与硬件架构的关系
    在这里插入图片描述

    ​ JMM只是一种抽象概念对硬件的实际存在不影响,而JMM中工作空间和主内存中的数据即可存在与寄存器中由可能存在于缓存(cache)中还可能存在于主内存(RAM)中,所以JMM与内存硬件架构是一种相互交叉的关系。

    4)Java内存模型的必要性

    ​ Java内存模型的作用:规范内存数据与工作空间数据的交互

    6 并发编程的三个重要特性

    原子性:指的是一个操作是不可中断的 ,不可分割。如x=10,就具有原子性,因为只有一步把10写入变量x中的操作。而y=x就不具有原子性,因为它分为把数据x读到工作空间,把x的值写入y两步操作。

    可见性:线程只能操作自己工作空间中的数据,自己工作空间中的数据只对自己可见不对其他线程可见。

    有序性:程序的执行顺序不一定是程序的书写顺序。在看似顺序执行的Java代码在JVM中可能出现优化,编译器或者CPU对操作指令出现重新排序。

    出现有序性的原因:

    • 编译重排序:在编译其为优化代码而对程序进行重排序
    • 指令重排序:在cpu执行过程中,对指令进行重排序,从而降低指令的执行时间,提高代码执行效率。

    无论是编译重排序还是指令重排序都要遵循as-if-seria原则

    as-if-seria:即不管怎么重排序(编译器和处理器为了提高并行度),(单线程)程序的执行结果不能被改变。

    7 JMM对三个特征的保证

    1)JMM与原子性

    因为多个原子性操作组合到一起是没有原子性的,所以为了保证多个原子性操作组合在一起的原子性;JMM保证其原子性的方式。

    • 通过关键字Synchronized,
    • JUC中的Lock类中的lock()方法

    2)JMM与可见性

    JMM保证可见性的方式:

    • Volatile:在JMM模型上实现MESI协议
    • Synchronized:加锁
    • JUC:JUC中的Lock类中的lock()方法

    3)JMM与有序性

    JMM保证有序性的方式:

    • Volatile

    • Synchronized

    • Happens-before原则:

      a.程序次序原则:程序次序改变不影响程序的结果

      b.锁定原则:后一次加锁必须等待前一次解锁

      c.Volatile原则:Volatile关键字修饰的内容,在程序中的位置不能改变

      d.传递原则:A—>B---->C ===> A---->C

    因为篇幅原因关于Synchronized,Volatile关键字和JUC有个内容在后续文章中给出。

  • 相关阅读:
    ubuntu的apt
    sudo命令
    MySQL导出数据到csv文件
    MySQL导出数据到文件报错
    git_backup.py gitlab项目备份
    java中图像与数组转换
    mongodb转elasticsearch
    impyla-0.14.2.2安装注意事项
    python3.7.3升级 with-openssl openssl-1.0.2a
    hadoop自带性能测试
  • 原文地址:https://www.cnblogs.com/wf614/p/11673812.html
Copyright © 2011-2022 走看看