Java——JMM 内存模型

JMM 内存模型

java内存模型(JMM)是线程间通信的控制机制.JMM定义了主内存和线程之间抽象关系。线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。Java内存模型的抽象示意图如下:

Java——JMM 内存模型

jvm内存结构

Java——JMM 内存模型

  1. 虚拟机栈:每个线程有一个私有的栈,随着线程的创建而创建。栈里面存着的是一种叫“栈帧”的东西,每个方法会创建一个栈帧,栈帧中存放了局部变量表(基本数据类型和对象引用)、操作数栈、方法出口等信息。栈的大小可以固定也可以动态扩展。当栈调用深度大于JVM所允许的范围,会抛出StackOverflowError的错误

  2. 本地方法栈:
      这部分主要与虚拟机用到的 Native 方法相关,一般情况下, Java 应用程序员并不需要关心这部分的内容。

  3. PC 寄存器:
      PC 寄存器,也叫程序计数器。JVM支持多个线程同时运行,每个线程都有自己的程序计数器。倘若当前执行的是 JVM 的方法,则该寄存器中保存当前执行指令的地址;倘若执行的是native 方法,则PC寄存器中为空。


  4.   堆内存是 JVM 所有线程共享的部分,在虚拟机启动的时候就已经创建。所有的对象和数组都在堆上进行分配。这部分空间可通过 GC 进行回收。当申请不到空间时会抛出 OutOfMemoryError。

  5. 方法区:
      方法区也是所有线程共享。主要用于存储类的信息、常量池、方法数据、方法代码等。方法区逻辑上属于堆的一部分,但是为了与堆进行区分,通常又叫“非堆”。

堆内存

jvm参数中只要使用-Xms,-Xmx等参数就可以设置堆的大小和最大值
堆内内存 = 新生代+老年代+持久代(java 8 不再使用,使用Metaspace 元空间)

堆中分代具体看java——GC文章
Java——JMM 内存模型
来源:http://www.jianshu.com/p/50be08b54bee

JMM三个特性

Java内存模型具有三个特性:原子性、可见性和有序性。
① 原子性,java内存模型保证了read、load、assign、use、store、write六个操作具有原子性,我们可以认为除了long和double类型外,对其他基本数据类型所对应的内存单元的访问读写都是原子的。但由于这个原子性的颗粒度太小,通常情况下我们需要更大颗粒度的原子性,这时就需要用锁来保证了。

② 可见性,在java内存模型中,简单说如果一条线程更改了共享变量的值,而其他线程能马上知道这个更改,我们则说这个变量具有可见性。一般来说有四种方式能保证变量的可见性,分别为volatile、synchronized、final和锁。首先谈谈volatile,被此关键词声明的变量,每当有任何更改时都将立即同步到主存中,而每个线程要使用这个变量时都要重新从主存刷新到工作内存,这样就确保了变量的可见性(当然,普通变量最终也会同步到主存,再由主存同步到每个线程的工作内存,只是这个最终可能比较“长久”,不能保证可见性);由于synchronized底层也是通过锁进行实现,所以synchronized和锁的本质是一样的,当一个线程释放一个锁时,将会强制刷新工作内存中的变量值到主存中,而当另一个线程获取此锁的时候将会强制重新装载此变量值,当然这两个线程获取的是同一个锁,这样就保证了变量的可见性;被final声明的变量一旦完成初始化,其他线程就能看到这个final变量。其实,可见性其实可以看成是一种机制,线程在进入/退出同步块程序时,它将发送/接收一个变量的更改。

③ 有序性,有序性指在线程内看方法的执行,所有的指令都是有序的,都按照一种串行方式执行,而在线程内观察其他线程,所有指令都是无序的,指令都可能交叉执行。Java中提供了volatile和synchronized两个关键词保证线程之间操作的有序性,而这个有序性仅仅是相对的,volatile禁止指令重排序,synchronized则保证持有同一个锁的同步块只能串行运行。
参考:http://www.voidcn.com/blog/wangyangzhizhou/article/p-2530650.html

内存模型规则

Java 语言的内存模型由一些规则组成,这些规则确定线程对内存的访问如何排序以及何时可以确保它们对线程是可见的。下面我们将分别介绍 Java 内存模型的重排序,内存可见性和 happens-before 关系。

重排序类型

  • 编译器生成指令的次序,可以不同于源代码所暗示的“显然”版本。
  • 处理器可以乱序或者并行的执行指令。
  • 缓存会改变写入提交到主内存的变量的次序。

内存可见性

由于现代可共享内存的多处理器架构可能导致一个线程无法马上(甚至永远)看到另一个线程操作产生的结果。所以 Java 内存模型规定了 JVM 的一种最小保证:什么时候写入一个变量对其他线程可见。
在现代可共享内存的多处理器体系结构中每个处理器都有自己的缓存,并周期性的与主内存协调一致。

Happens-before 关系(先发生原则)

happens-before 关系保证:如果线程 A 与线程 B 满足 happens-before 关系,则线程 A 执行动作的结果对于线程 B 是可见的。如果两个操作未按 happens-before 排序,JVM 将可以对他们任意重排序。
下面介绍几个与理解 ConcurrentHashMap 有关的 happens-before 关系法则:

  1. 程序次序法则:如果在程序中,所有动作 A 出现在动作 B 之前,则线程中的每动作 A 都 happens-before 于该线程中的每一个动作 B。
  2. 监视器锁法则:对一个监视器的解锁 happens-before 于每个后续对同一监视器的加锁。
  3. Volatile 变量法则:对 Volatile 域的写入操作 happens-before 于每个后续对同一 Volatile 的读操作。
  4. 传递性:如果 A happens-before 于 B,且 B happens-before C,则 A happens-before C。