浅谈 Java 内存模型
Java 内存模型(JMM)描述了 JVM 如何使用计算机的内存(RAM)。JVM 是一个完整计算机的模型,因此该模型包含了内存模型的设计 —— JMM。
如果要正确地设计并发程序,了解 JMM 非常重要。JMM 描述了不同线程间如何以及何时可以看到其它线程写入共享变量的值,以及如何在必要时同步访问共享变量。
最初的 JMM 设计不充分,因此 JMM 在 Java 1.5 进行了修订。此版本的 JMM 仍在 Java 8 中使用。
Java Memory Model 内部实现
JVM 内部使用的 JMM 将内存划分为线程栈和堆。下图从逻辑角度说明了 JMM:

在 JVM 中运行的每个线程都有它自己的线程栈,线程栈包含了线程调用了哪些方法以到达当前执行点的信息,我们把它成为“调用栈(Call Stack)“。当线程执行其代码时,调用栈会发生变化。
线程栈还包含了正在执行的每个方法的所有的局部变量(调用栈上的所有方法)。一个线程只能访问它自己的线程栈,由线程创建的局部变量对于创建它的线程以外的所有其他线程都是不可见的。即使两个线程正在执行完全相同的代码,两个线程仍将在各自的线程栈中创建自己的局部变量。因此,每个线程都有自己的每个局部变量的版本。
基本类型(boolean,byte,short,char,int,long,float,double)完全存储在线程栈里,因此对其他线程是不可见的。一个线程可以将一个基本类型的变量副本传递给另一个线程,但它不能共享原始局部变量本身。
堆包含了 Java 应用程序中创建的所有对象,不管对象是哪个线程创建的,这包括基本类型的包装版本(如 Byte,Integer,Long 等)。无论对象是创建成局部变量,还是作为另一个对象的成员变量被创建,对象都存储在堆中。
下图说明了调用栈和局部变量存储在线程栈中,而对象存储在堆中。

局部变量如果是基本类型,这种情况下,变量完全存储在线程栈上。
局部变量如果是对象的引用,这种情况下,引用(局部变量)存储在线程栈上,但对象本身存储在堆上。
对象中可能包含方法,而这些方法中可能包含局部变量,这种情况下,即使方法所属的对象存储在堆上,但这些局部变量却是存储在线程栈上的。
对象的成员变量与对象本身一起存储在堆上,当成员变量是基本类型以及是对象的引用时都是如此。
静态类型变量与类定义一起存储在堆上。
所有线程通过拥有对象引用去访问堆中的对象。当一个线程有权访问一个对象时,它也能访问该对象的成员变量。如果两个线程同一时间调用同一对象的一个方法,它们都可以访问该对象的成员变量,但每个线程都有自己局部变量的副本。
这是一个说明上述要点的图表:

两个线程各有一组局部变量,其中一个局部变量(Local Variable 2)指向堆中的共享对象(Object 3)。两个线程各自对同一各对象拥有不同的引用,它们的引用是局部变量,因此它们存储在各自线程的线程栈中。但是,这两个不同引用指向堆中的同一个对象。
请注意,共享对象(Object 3)将 Object 2 和 Object 4 作为成员变量引用(如从 Object 3 到 Object 2 和 Object 4 的箭头所示),通过对象 3 中的这些成员变量引用,两个线程可以访问对象 2 和 对象 4。
上图还显示了一个局部变量指向堆中的两个不同对象。这种情况下,引用指向两个不同的对象(Object 1 和 Object 5),而不是同一个对象。理论上,如果两个线程都引用了两个对象,那两个线程都可以访问对象 1 和 对象 5。但在上图中,每个线程只引用了两个对象中的一个。
This chapter requires login to view full content. You are viewing a preview.
Login to View Full Content