关于java内存模型的浅析

    首先本篇介绍的是java内存模型而不是java的内存结构,这两个是不同的,很多人将这两个概念搞混。所以就先进行介绍一下java内存结构,这样可以和内存模型有一些区分:

    java内存结构

    

    由Java虚拟机规范定义。描述的是Java程序执行过程中,由JVM管理的不同数据区域。各个区域有其特定的功能。如下:

        1、以上是Java虚拟机规范,不同的虚拟机实现会各有不同,但是一般会遵守规范。

        2、规范中定义的方法区,只是一种概念上的区域,并说明了其应该具有什么功能。但是并没有规定这个区域到底应该处于何处,对于不同的虚拟机可以有不同的实现。

        3、不同版本的方法区所处位置不同,上图中划分的是逻辑区域,并不是绝对意义上的物理区域。因为某些版本的JDK中方法区其实是在堆中实现的。

        4、运行时常量池用于存放编译期生成的各种字面量和符号应用。但是,Java语言并不要求常量只有在编译期才能产生。比如在运行期,String.intern也会把新的常量放入池中。

        5、除了以上介绍的JVM运行时内存外,还有一块内存区域可供使用,那就是直接内存。Java虚拟机规范并没有定义这块内存区域,所以他并不由JVM管理,是利用本地方法库直接在堆外申请的内存区域。

        6、堆和栈的数据划分也不是绝对的,如HotSpot的JIT会针对对象分配做相应的优化。

    下面进入正题java的内存模型

    Java内存模型

    Java内存模型是英文Java Memory Model(JMM)翻译过来的。其实JMM并不像JVM内存结构一样是真实存在的。他只是一个抽象的概念。

    Java内存模型看上去和Java内存结构(JVM内存结构)差不多,很多人会误以为两者是一回事儿,这也就导致面试过程中经常答非所为。其中Java堆和方法区的区域是多个线程共享的数据区域。也就是说JMM是和多线程相关的,多个线程可能可以操作保存在堆或者方法区中的同一个数据。即“Java的线程间通过共享内存进行通信”。它描述了一组规则或规范,这个规范定义了一个线程对共享变量的写入时对另一个线程是可见的。

    Java的多线程之间是通过共享内存进行通信的,而由于采用共享内存进行通信,在通信过程中会存在一系列如可见性、原子性、顺序性等问题,而JMM就是围绕着多线程通信以及与其相关的一系列特性而建立的模型。JMM定义了一些语法集,这些语法集映射到Java语言中就是volatile、synchronized等关键字。

    主内存和工作内存:

  Java内存模型的主要目标是定义程序中各个变量的访问规则,即在JVM中将变量存储到内存和从内存中取出变量这样的底层细节。此处的变量与Java编程里面的变量有所不同步,它包含了实例字段、静态字段和构成数组对象的元素,但不包含局部变量和方法参数,因为后者是线程私有的,不会共享,当然不存在数据竞争问题(如果局部变量是一个reference引用类型,它引用的对象在Java堆中可被各个线程共享,但是reference引用本身在Java栈的局部变量表中,是线程私有的)。为了获得较高的执行效能,Java内存模型并没有限制执行引起使用处理器的特定寄存器或者缓存来和主内存进行交互,也没有限制即时编译器进行调整代码执行顺序这类优化措施。

  JMM规定了所有的变量都存储在主内存(Main Memory)中。每个线程还有自己的工作内存(Working Memory),线程的工作内存中保存了该线程使用到的变量的主内存的副本拷贝,线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量(volatile变量仍然有工作内存的拷贝,但是由于它特殊的操作顺序性规定,所以看起来如同直接在主内存中读写访问一般)。不同的线程之间也无法直接访问对方工作内存中的变量,线程之间值的传递都需要通过主内存来完成。

    在Java中,JMM是一个非常重要的概念,正是由于有了JMM,Java的并发编程才能避免很多问题。下面对Java内存模型做更加详细的介绍了。

    处理器的内存模型:

    顺序一致性内存模型是一个理论参考模型,JMM和处理器内存模型在设计时通常会以顺序一致性内存模型为参照,JMM和处理器内存模型会对顺序一致性模型做一些放松,完全按照顺序一致性模型来实现处理器和JMM,很多的处理器和编译器优化都要被禁止对效率是一种影响。

    根据对不同类型的读/写操作组合的执行顺序的放松,可以分为如下类型:

        1.放松程序中写-读操作的顺序,产生了Total Store Order内存模型(TSO)

        2.继续放松程序中写-写操作的顺序,产生了Partial Store Order内存模型(PSO)

        3.继续放松程序中的读-写和读-读操作的顺序,由此产生了Relaxed Memory Order 内存模型和PowerPC内存模型

    在这里进行的处理器读/写放松是以两个操作之间,不存在数据依赖为前提的。所有处理器都不会对存在数据依赖的两个内存操作做重排序,因为处理器要遵守as-if-serial语义。

    所有处理器内存模型都允许写-读重排序还同时允许更早读到处理器的写,因为都使用了写缓存区,写缓存区会导致写-读的重排序,因为写缓存区只对当前处理器可见,这个特性导致当前处理器可以比其他处理器先看到临时保存在自己写缓存区中的写。

    各种处理器内存模型越是追求性能的处理器,内存模型设计得就会越弱,因为这些处理器希望内存模型对它们的束缚越少越好,这样就可以尽量提高性能。常见的处理器内存模型比JMM要弱,导致JMM在不同的处理器模型平台上如果想向程序员展示一个一致的内存模型,就需要JMM在不同的处理器中插入的内存屏障的数量和种类不同。JMM屏蔽了不同处理器的内存模型的差异,加强了约束使java的使用者拥有了一个一致的内存模型。

    JMM是一个语言级的内存模型,处理器内存模型是硬件级的内存模型,顺序一致性内存模型是一种理论参考模型。常见的4种处理器内存模型(TSO、PSO、RMO、PowerPC)比常用的3种语言内存模型(JMM、C++11MM、CLR2.0MM)要弱,处理器内存模型和语言内存模型都比顺序内存模型要弱。越是追求性能的语言内存模型就会越弱。

    java程序的内存可见性保证分为3类:

        (1)单线程程序:不会出现内存可见性的问题,编译器和runtime和处理器会共同确保单线程程序的执行结果,与该程序在顺序一致性模型中的执行结果相同。

        (2)正确同步的多线程程序:正确同步的多线程程序的执行将具有顺序一致性,结果相同,是JMM通过限制编译器和处理器的重排序来提供内存可见性保证。

        (3)未同步/未正确同步的多线程程序:JMM为它们提供了最小安全性保障,线程执行时读取到的值,要么是之前某个咸亨写入的要么是默认值。

    只要多线程程序是正确同步的,JMM保证该程序在任意的处理平台上的执行结构与该程序在顺序一致性内存模型上的执行结果一致。

    JSR-133对JDK5之前的旧内存模型进行了修补,主要修补了两个方面:

        1.增强了volatile的语义,在旧内存模型允许volatile变量与普通变量重排序,之后严格限制volatile变量和普通变量的重排序,使volatile的写-读和锁的释放-获取具有相同的内存语义

        2.增强final内存语义,在旧的内存模型里多次读同一个final变量的值可能会不相同,所以为final增加了两个重排序规则,在保证final引用不会从构造函数内溢出的情况下,final具有了初始化安全性。

所以JMM和顺序一致性内存模型只有在未同步/未正确同步的多线程程序结果才不相同。

猜你喜欢

转载自blog.csdn.net/ZytheMoon/article/details/89888444
今日推荐