主要看懂这篇文章全面理解Java内存模型(JMM)及volatile关键字。
理解三张图
内存区域图
方法区(静态区)主要存放静态变量,类信息(包含成员方法信息)等。存放的都是程序中唯一的元素。
堆主要存放对象实例,也是共享区域。
虚拟机栈存放每个线程私有的变量,引用,返回值等信息。
本地方法栈,与Native方法相关,一般情况下,无需关心。
程序计数器主要代表当前线程所执行的字节码行号指示器。字节码解释器工作时,通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
关于虚拟机栈有个问题还是要注意下:
每个方法执行时都会创建一个栈桢来存储方法的的变量表、操作数栈、动态链接方法、返回值、返回地址等信息。每个方法从调用直结束就对于一个栈桢在虚拟机栈中的入栈和出栈过程。
值得注意的是方法的调用是需要从方法区中获取成员方法的相关信息之后再到栈中进行创建变量内存等。
内存模型图
首先,内存模型和内存区域是两个不同层次的概念,相似之处在于都存在共享内存区域和私有内存区域。内存模型中的主内存主要是内存区域的共享数据区域,工作内存是内存区域的私有数据区域。
主内存中主要存储的是Java实例对象,所有线程创建的实例对象都存放在主内存中,不管该实例对象是在何处创建的,当然也包括了共享的类信息、常量、静态变量。由于是共享数据区域,多条线程对同一个变量进行访问可能会发现线程安全问题。注意这里的局部变量必须是创建的实例对象才保存在堆中(主内存中)。
工作内存主要存储当前方法的所有本地变量信息(工作内存中存储着主内存中的变量副本拷贝),每个线程只能访问自己的工作内存,即线程中的本地变量对其它线程是不可见的,就算是两个线程执行的是同一段代码,它们也会各自在自己的工作内存中创建属于当前线程的本地变量,当然也包括了字节码行号指示器、相关Native方法的信息。注意由于工作内存是每个线程的私有数据,线程间无法相互访问工作内存,因此存储在工作内存的数据不存在线程安全问题。
有个问题注意一下,都知道实例对象存储在堆中,包括成员变量,不管这个成员变量是什么数据类型的,但是成员方法中的局部变量(基本数据类型)为什么是存储在栈区中?因为,方法区保存成员方法的相关信息,但是不会保存成员方法内的变量,这些变量只有在被调用到的时候才会分配内存,随着方法的结束而销毁。而成员变量是随着类对象的创建就分配了内存的,并随着垃圾回收而消失。
工作过程如下:
在主内存中的实例对象可以被多线程共享,倘若两个线程同时调用了同一个对象的同一个方法,那么两条线程会将要操作的数据拷贝一份到自己的工作内存中,执行完成操作后才刷新到主内存。
Java内存模型与硬件内存架构的关系图
通过对前面的硬件内存架构、Java内存模型以及Java多线程的实现原理的了解,我们应该已经意识到,多线程的执行最终都会映射到硬件处理器上进行执行,但Java内存模型和硬件内存架构并不完全一致。对于硬件内存来说只有寄存器、缓存内存、主内存的概念,并没有工作内存(线程私有数据区域)和主内存(堆内存)之分,也就是说Java内存模型对内存的划分对硬件内存并没有任何影响,因为JMM只是一种抽象的概念,是一组规则,并不实际存在,不管是工作内存的数据还是主内存的数据,对于计算机硬件来说都会存储在计算机主内存中,当然也有可能存储到CPU缓存或者寄存器中,因此总体上来说,Java内存模型和计算机硬件内存架构是一个相互交叉的关系,是一种抽象概念划分与真实物理硬件的交叉。(注意对于Java内存区域划分也是同样的道理)。简单来说就是没有什么关系。
总结
文章还有关于原子性,可见性,重排等概念的解释,在此不做总结了,有兴趣可以自己看下。












网友评论