JVM:内存模型

一、目标

        了解 JVM 的内存模型是什么,以及它在并发编程方面有何作用。

二、前言

        我们知道我们的程序是运行在计算机上,那免不了要与计算交互。

        内存模型,它是一个比较古老的东西,是与计算机硬件有关的一个概念。在了解JVM 的内存模型之前,我们先看看物理计算机内存模型是啥。

1、CPU 和缓存一致性

        计算机在执行程序的时候,每条指令都是在 CPU 中执行的,而执行的时候,又免不了要和数据打交道,而计算机上面的数据,是存放在主存中的,也就是计算机的物理内存。

        随着 CPU 技术的发展,从内存各种读取和写入数据的速度与 CPU 的执行速度越来越不对等,这导致了 CPU 每次操作内存都要耗费很多的等待时间。

        所以,现在计算机系统都不得不加入一层读写速度尽可能接近 CPU 运算速度的高速缓存(Cache)来作为内存与 CPU 之间的缓冲。

        那么,程序的执行过程就变成了:

        当程序在运行过程中,将运算需要使用到的数据从内存复制一份到 CPU 的高速缓存当中,让 CPU 的运算能快速进行,当运算结束后再从缓存同步回内存中,这样 CPU 就无须等待缓慢的内存读写了。

        CPU 的缓存结构如图所示:

        在 CPU 和内存之间增加了缓存,虽然很好地解决了 CPU 和内存的速度矛盾,但也因此引入了一个问题:缓存一致性问题。也就是说,在多核 CPU 中,每个核的自己缓存中,关于同一个数据的缓存内容可能不一致。

2、处理器优化和指令重排

        除了增加高速缓存之外,还有一种硬件问题也比较重要。

        计算机为了使得 CPU 内部的运算单元能尽量被充分利用,CPU 可能会对输入代码进行乱序执行优化,CPU 会在计算之后将乱序执行的结果重组,保证该结果与顺序执行的结果是一致,但并不保证程序中各个语句计算的先后顺序与输入代码中的顺序一致。因此,如果存在一个计算任务依赖另外一个计算任务的中间结果,那么其顺序性并不能靠代码的先后顺序来保证。

        与 CPU 的乱序执行优化类似,JVM 的即时编译器中也有类似的指令重排序优化。

3、计算机的内存模型

        如上面所提到的问题,缓存一致性、处理器优化的指令重排等,这些都是因硬件的不断升级导致的。那么,有没有什么机制能够很好的解决这些问题?

        为了保证共享内存的正确性(原子性、可见性、有序性),内存模型定义了共享内存系统中多线程程序读写操作行为的规范。通过这些规则来规范对内存的读写操作,从而保证执行执行的正确性。它与 CPU有关、与缓存有关、与并发有关、与编译器也有关,它解决了 CPU 多级缓存、处理器优化、指令重排等导致的内存访问问题,保证了并发场景下的一致性、原子性和有序性。

        内存模型解决并发问题主要采用两种方式:限制处理器优化和使用内存屏障。

三、Java 内存模型

        前面我们提到计算机内存模型,它是解决多线程场景下并发问题的一个重要规范。那么具体是怎么实现的呢?不同的编程语言,在实现上可能也有所不同。

        Java 程序运行在 Java 虚拟机上,Java 虚拟机规范也试图定义一种 Java 内存模型(Java Memory Model,JMM)来屏蔽掉各种硬件和操作系统的内存访问差异,保证了 Java 程序在各种平台下对内存的访问都能保证效果一致。

        注意,这里提到的 Java 内存模型,一般指的是 JDK 5 开始使用的新的内存模型。关于它的描述,感兴趣可以参看这份 PDF 文档

1、内存模型的目标

        Java 内存模型规定了所有变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存。

        不同的线程之间无法直接访问对方工作内存的变量,线程间变量值的传递均需要通过自己的工作内存和主内存进行数据同步来完成。

        即,JMM就作用于工作内存和主内存之间数据同步过程,它规定了如何做数据同步以及什么时候做数据同步。

在这里插入图片描述
        注意,这里的主内存与工作内存与《JVM:内存结构》中的堆、栈、方法区等并不是同一层次的内存划分,无法直接类比。如果要勉强对应的话,从变量、主内存、工作内存的定义来看,主内存主要对应与堆中的对象实例数据部分,而工作内存则对应于虚拟机栈中的部分区域。

        总结下,JMM 是一种规范,目的是解决由于多线程通过共享内存进行通信时,存在的本地内存数据不一致、编译器会对代码指令重排序、处理器会对代码乱序执行等带来的问题。

2、内存模型的实现

        关于主内存与工作内存之间具体的交互协议,即一个变量如何从主内存拷贝到工作内存、如何从工作内存同步回主内存之类的实现细节。

        了解 Java 多线程的都知道,在 Java 提供了一系列和并发处理相关的关键自,比如volatilesynchronizedfinalconcurrent包等。这些就是 JMM 封装了底层的实现后提供给开发者使用的一些手段。

        在并发编程的时候,我们可以直接使用synchronized等关键字来控制并发,从不需要去关心底层的编译器优化、缓存一致性等问题。所以,JMM,除了定义一套规范,还提供了一系列原语,封装了底层实现后,供开发者直接使用。

并发编程的问题

        前面我们所提到的缓存一致性、处理器优化和指令重排等问题。在程序中,其实就是并发编程所遇到的问题:

  • 原子性
    在 Java 中,为了保证原子性,提供了两个高级的字节码指令monitorentermonitorexit。这两个字节码,在 Java 中封装后对应的关键字就是synchronized

        因此,在 Java 中可以使用synchronized来保证方法和代码块内的操作是原子性的。

  • 可见性
    JMM 是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值的这种依赖主内存作为传递媒介的方式来实现的。

        Java 中的volatile关键字提供了一个功能,那就是被其修饰的变量在被修改后可以立即同步到中内存,被其修饰的变量在每次使用之前都从主内存刷新。因此,可以使用volatile来保证多线程操作时变量的可见性。

        除了volatilesynchronizedfinal两个关键字也可以实现可见性。

  • 有序性
    在Java中,可以使用synchronized和volatile来保证多线程之间操作的有序性。实现方式有所区别:

    • volatile关键字会禁止指令重排。

    • synchronized关键字保证同一时刻只允许一条线程操作。


引用

《深入理解Java虚拟机:JVM高级特性与最佳实践》
《Java并发编程的艺术》
https://blog.csdn.net/hollis_chuang/article/details/80880118

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值