http://tutorials.jenkov.com/java-concurrency/java-memory-model.html
java虚拟机和java api构成一个的平台,屏蔽之下的操作系统。而java虚拟机是一个计算机的模型,自然包括他独特的内存模型。
从上面可以看出,java内存模型包括栈区、堆区。但是不仅仅这些,还包括方法区和本地方法区,但是这里所讨论的并没有涉及到。
相同程序中的不同线程共享一个堆,而都拥有一个栈。如上图所示。
栈中存放着每个方法的调用信息和局部变量(包括对象引用)。每个函数的调用过程都在栈中记录着。方法中的局部变量也存放在栈中,比如基本类型(primitive type)的变量将会存放在栈中。对象引用也将存放在栈中,而被引用的对象则是存放在堆中。
里面存放的都是新建的对象,因为不同线程都共享同一堆,因此这些对象可能由不同的线程创建。更具体一点就是来自不同线程中方法中的局部变量引用的对象,或实例对象中成员变量引用的对象。
上面讲到了一些变量的存放位置,这里将更详细给出。
基本类型的局部变量存放在所属线程的栈中
引用类型的局部变量存在在所属线程的栈中,而被引用对象存在在共享的堆中。
对象,指向该对象的引用如果是局部变量,则存放在栈中,如果是成员变量,则存放随创建该对象的对象一起存放在堆中(读起来有点拗口)。
对象的成员变量,已在上一段话中给出,就是与拥有该成员变量的对象一同存放在堆中。
类型,一个类型是Class类的对象。这也许很难理解,为什么类型也属于对象呢?这就是java的一个特变,当初从c++转到java,理解它可费了一番劲(这里涉及到了一个问题,先有对象还是先有类呢?这是先有鸡蛋还是先有鸡的问题)。这就涉及到了类加载的过程,当类加载器从文件系统、网络或数据库中获得java字节码文件(.class文件)后,将该字节码读入方法区,并在堆中创建一个代表该类型的一个Class类型对象,该对象代表该类型,我们可以通过该对象获得类的相关信息,比如静态变量等,或者通过该对象创建实例对象。因此,这就是为什么类型是Class类的一个对象的原因。
因此,类的静态成员也是存放在堆中的,与类型一起存放。
至于方法,当然存放在方法区啦。
上面那幅图中给出了部分变量与存放位置的关系。这就是java的内存模型。
硬件内存架构和java内存模型是不同的,但是只有理解了硬件内存架构和java内存模型才能更好的理解并发时会出现的问题。
下面说说我的理解。java内存模型是程序员在不涉及并发编程时能够看到的java内存存放方式,而硬件内存架构为了更好的提高访存速度,因此java编译器都会针对硬件内存架构进行优化。这些优化对于当线程是很有用的,它极大地提高了cpu访存的效率,但是在多线程中却会造成一些问题,比如线程之间的可见性、竞争条用的出现。因此多线程开发中需要理解硬件内存架构,才能理解同步块和volatile的作用。(现在讲的可能听不懂,但是可以在看了下文之后再来回顾)
下面是一个简化的硬件内存架构(虽然简化且和我们计算机使用的不一样,但是在功能上和并发开发中是一致的,简化的更好理解,且可扩展)
现代的计算机经常拥有多个cpu,也许这些cpu还有多个核,也许每个核还可以通过超线程技术当成两个核使用。。更有甚者,还有同时多线程结构(SMT,Simultaneous Multi Thread)的处理器(一个核心,但是通过硬件上下文来模拟多个处理核心的效果)。。。我的电脑只有一个cpu,cpu里有两个核,每个核可以超线程,因此一共四个逻辑处理器。实际上呢,他根本达不到四个处理器的运行效率。
每个cpu都有寄存器,对寄存器的访问非常快。实际上cpu并不是直接访问内存的,而是访问高速缓存,而缓存又分为Level 1 and Level 2(目前也有Level 3),cpu直接访问的是L1缓存。在多核处理器中,每个核访问的L1缓存或许共享或许不共享,在多cpu系统中,缓存都不共享。但在并发编程中,都可以理解成上述那幅图,它里面的实际结构不用关心。也就是说,可以直接把逻辑处理器理解为当个cpu,每个cpu拥有一个缓存、一组寄存器,共享主存。比如我的电脑,4个逻辑处理器理解为单独的4个cpu。如果学过计算机组成原理和操作系统,那么就可以理解为何可以如此理解了。
一般情况下,当cpu读写都是对缓存操作的,读的时候先看缓存中有没有,如果没有,则从主存中读取一块(这一块也叫缓存行 cache line),读到缓存中,cpu再从缓存中读取。当写的时候,cpu是写入到缓存中的,然后在某一刻写入到内存中。这是一般情况啦,特殊情况就是使用了同步机制,因此被强制从主存中读变量,直接写入到主存,但会影响效率。
从上面可以看出两者是不同的。硬件内存架构并不区分栈和堆。因此,一开始栈和堆位于主存,在cpu的读写过程中,栈和堆的部分内存会可能会出现在cpu缓存和寄存器中,如图所示:
因为所有变量都在堆和栈中,因此他们都可能出现在主存、缓存、寄存器中,也就是说存在多个副本。一般在单线程程序中没有什么问题,在多线程中就会出现可见性、竞争条件等问题。不详细讲了,链接里都有。
还有一个重要的内容就是同步块(synchronized block)怎么保证不同线程之间的可见性的,懒得翻译了
To solve this problem you can use a Java synchronized block. A synchronized block guarantees that only one thread can enter a given critical section of the code at any given time. Synchronized blocks also guarantee that all variables accessed inside the synchronized block will be read in from main memory, and when the thread exits the synchronized block, all updated variables will be flushed back to main memory again, regardless of whether the variable is declared volatile or not.
本来想写个小总结的。。。怎么写了这么多。。
http://tutorials.jenkov.com/java-concurrency/java-memory-model.html
http://raising.iteye.com/blog/2377709
《操作系统设计原理》 鞠时光 科学出版社