Java 内存模型引入-从堆栈角度引入JMM

一、JMM引入

(一)从堆栈说起

JVM内部使用的Java内存模型在线程栈和堆之间划分内存。 此图从逻辑角度说明了Java内存模型:

JVM图

(二)堆栈里面放了什么?

       线程堆栈还包含正在执行的每个方法的所有局部变量(调用堆栈上的所有方法)。 线程只能访问它自己的线程堆栈。 由线程创建的局部变量对于创建它的线程以外的所有其他线程是不可见的。 

       每个线程都有自己的每个局部变量的版本基本类型的所有局部变量(boolean,byte,short,char,int,long,float,double)完全存储在线程堆栈中,因此对其他线程不可见一个线程可以将一个基本类型变量的副本传递给另一个线程,但它不能共享原始局部变量本身。

        堆包含了在Java应用程序中创建的所有对象,无论创建该对象的线程是什么。 这包括基本类型的包装类(例如Byte,Integer,Long等)。 无论是创建对象并将其分配给局部变量,还是创建为另一个对象的成员变量,该对象仍然存储在堆上。

       局部变量可以是基本类型,它完全保留在线程堆栈上局部变量也可以是对象的引用,引用(局部变量)存储在线程堆栈中,但是对象本身存储在堆(Heap)上。对象的成员变量与对象本身一起存储在堆上。 不管是成员变量是基本类型时,以及它是对象的引用时都是如此。静态类变量也与类定义一起存储在堆上。

(三)线程栈如何访问堆上对象?

        所有具有对象引用的线程都可以访问堆上的对象。 当一个线程有权访问一个对象时,它也可以访问该对象的成员变量。 如果两个线程同时在同一个对象上调用一个方法,它们都可以访问该对象的成员变量,但每个线程都有自己的局部变量副本。

        两个线程各自对同一对象具有不同的引用,它们的引用是局部变量,因此存储在每个线程的线程堆栈中(在每个线程堆栈上)。 但是,这两个不同的引用指向堆上的同一个对象。

二、JMM与硬件内存结构关系

(一)硬件内存结构简介

       现代硬件内存架构与内部Java内存模型略有不同。 了解硬件内存架构也很重要,所以了解Java内存模型如何与其一起工作。 

现代计算机硬件架构的简化图

       每个CPU基本上都包含一组在CPU内存中的寄存器。 CPU可以在这些寄存器上执行的操作比在主存储器中对变量执行的操作快得多。 每个CPU还具有CPU高速缓存存储器层CPU高速缓存存储器介于内部寄存器和主存储器的速度之间。 

         计算机包含主存储区(RAM),所有CPU都可以访问主内存。 主存储区通常比CPU的高速缓存存储器大得多,同时访问速度也就较慢。通常,当CPU需要访问主存储器时,它会将部分主存储器读入其CPU缓存甚至可以将部分缓存读入其内部寄存器,然后对其执行操作当CPU需要将结果写回主存储器时,它会将值从其内部寄存器刷新到高速缓冲存储器,并在某些时候将值刷新回主存储器。

(二)JMM与硬件内存连接 - 引入

        Java内存模型和硬件内存架构是不同的。 硬件内存架构不区分线程堆栈和堆。 在硬件上,线程堆栈和堆都位于主存储器中。 线程堆栈和堆的一部分有时可能存在于CPU高速缓存和内部CPU寄存器中。 这在图中说明:

(三)JMM与硬件内存连接 - 对象共享后的可见性

       如果两个或多个线程共享一个对象,而没有正确使用volatile声明或同步,则一个线程对共享对象的更新可能对其他线程不可见

       共享对象最初存储在主存储器中在CPU上运行的线程将共享对象读入其CPU缓存中它在那里对共享对象进行了更改。 只要CPU缓存尚未刷新回主内存,共享对象的更改版本对于在其他CPU上运行的线程是不可见的。 要解决此问题,使用Java的volatile关键字 volatile关键字可以确保直接从主内存读取给定变量,并在更新时始终写回主内存。

(四)JMM与硬件内存连接 - 竞态条件

       如果两个或多个线程共享一个对象,并且多个线程更新该共享对象中的变量,则可能会出现竞态。如果这些增量是按先后顺序执行的,则变量计数将增加两次并将原始值+ 2写回主存储器。但是,两个增量同时执行而没有适当的同步,数据会存在偏差。要解决此问题,您可以使用Java synchronized块。 

       同步块保证在任何给定时间只有一个线程可以进入代码的给定关键部分。 同步块还保证在同步块内访问的所有变量都将从主存储器中读入,当线程退出同步块时,所有更新的变量将再次刷新回主存储器,无论变量是不是声明为volatile。

你可能感兴趣的:(Java 内存模型引入-从堆栈角度引入JMM)