java内存模型
来源:互联网 发布:网络配线架安装电话 编辑:程序博客网 时间:2024/06/06 02:11
java内存模型(Java Memory Model,简称JMM)隶属于JVM(Java虚拟机),它定义了Java虚拟机在计算机内存(RAM)中的工作方式,也就是说它规范了Java虚拟机与计算机内存之间是如何协同工作的。JMM规定了如何和何时可以看到由其他线程修改过后的共享变量的值,以及在必须时如何同步地访问共享变量 。
JMM把JVM内部划分为线程栈和堆,如下图所示:
每个运行在JVM中的线程都拥有自己的线程栈(Thread Stack),线程仅能访问自己的线程栈,且创建的本地变量仅对自己可见。
所有原始类型(boolean,byte,short,char,int,long,float,double)的本地变量都直接保存在线程栈当中,它们的值在各个线程之间独立。也就是说,一个线程可以传递一个原始类型的本地变量的副本给另一个线程,但它们之间是无法共享这个本地变量本身的。
堆区包含了Java应用创建的所有对象的信息,此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。不管对象是哪个线程创建的,属于一个成员变量还是方法中的本地变量,都会被存储在堆区。
一个本地变量也有可能是一个对象的引用,这种情况下,这个本地引用会被存储到栈中,但是对象本身仍然存储在堆区(比如下图中的本地变量localVariable2和它所指向的对象object3)。
一个对象的成员方法中的本地变量存储在栈区;一个对象的成员变量不管是原始类型还是包装类型,都会被存储到堆区。
堆中的对象可以被多个线程共享,如果一个线程获得一个对象的引用,它便可访问这个对象的成员变量(比如本地变量localVariable2存储了Object3对象的引用,所以它可以访问Object3对象的成员变量object2和object4)。如果两个线程同时调用了同一个对象的同一个方法,那么这两个线程便可同时访问这个对象的成员变量,但是对于本地变量,每个线程都会拷贝一份到自己的线程栈中。
以上描述总结如下图:
public class MyRunnable implements Runnable() { public void run() { methodOne(); } public void methodOne() { //localVariable1是原始类型的本地变量,保存在线程栈中 int localVariable1 = 45; //localVariable2是对象的引用,指向堆上的对象Object3 Object3 localVariable2 = Object3.object3; methodTwo(); } public void methodTwo() { //localVariable3是对象的引用 Integer localVariable3 = new Integer(99); }}public class Object3 { //通过静态变量设置localVariable2指向一个对象引用,仅存在一个静态变量的一份拷贝,localVariable2的两份拷贝都指向同一个实例 public static final Object3 object3 = new Object3(); //成员变量不管是原始类型还是包装类型,都随对象存放在堆上 public Integer object2 = new Integer(22); public Integer object4 = new Integer(44); public long member1 = 12345; public long member2 = 67890;}
Java内存模型运行在计算机硬件上,计算机硬件内存架构如下图所示:
现代计算机一般都有多个CPU,而且每个CPU还有可能包含多个核心。因此,如果我们的应用是多线程的话,这些线程可能会在各个CPU核心中并行运行。
CPU Registers : CPU内部的一组CPU寄存器,也就是CPU的储存器。
CPU Cache Memory : CPU缓存,存在于主存和CPU寄存器之间,某些CPU可能有多个缓存层(一级缓存和二级缓存)。
RAM - Main Memory : 计算机的主存也称作RAM,所有的CPU都能够访问主存,而且主存比上面提到的缓存和寄存器大很多。
CPU操作寄存器速度 > 操作CPU缓存的速度 > 操作计算机主存速度。
当一个CPU需要访问主存时,会先读取一部分主存数据到CPU缓存,进而在读取CPU缓存到寄存器。当CPU需要写数据到主存时,同样会先flush寄存器到CPU缓存,然后再在某个时间点把缓存数据flush到主存。
Java内存模型与计算机硬件内存架构关系如下图所示:
对于硬件内存架构,所有的线程栈和堆都分布在主存中,部分线程栈和堆可能有时候会出现在CPU缓存中和CPU内部的寄存器中。
当对象和变量存储到计算机的各个内存区域时,主要的两个问题是:
1.线程对共享对象修改的可见性。
2.race condition:两个或多个进程读写某些共享数据,而最后的结果取决于进程运行的精确时序,称为竞争条件。
线程对共享对象修改的可见性
当多个线程同时操作同一个共享对象时,如果没有合理的使用volatile和synchronized关键字,一个线程对共享对象的更新有可能导致其它线程不可见。
比如线程1先从主存中读取共享对象obj到缓存,之后对该共享对象的count属性做修改操作,此时共享变量的新值仍未flush到主存,若有其他线程从主存中访问该共享对象,则无法看到最新值,也就是说线程1对共享对象的修改对于其他线程是不可见的。
这个问题可以考虑使用Java中的volatile关键字,它可以保证直接从主存中读取一个变量,如果这个变量被修改后,总是会被写回到主存中去。
race condition
比如现在有线程1和线程2两个线程,它们都读取主存中的同一共享对象到缓存中,并对该共享对象的count属性执行+1操作。
如果这两个线程是串行执行的话,count的值最终会+2;
如果它们是并行执行的话,count的值最终只会+1。
可以考虑使用java中的synchronized代码块来解决这个问题,它可以保证同一个时刻只能有一个线程进入代码竞争区,也能保证代码块中所有变量都将会从主存中读,当线程退出代码块时,对所有变量的更新将会flush到主存,不管这些变量是不是volatile类型的。
- java内存模型 内存屏障
- Java内存模型
- Java内存模型
- java内存模型详解
- 12.Java内存模型
- java内存模型详解
- Java内存模型
- Java内存模型详解
- java 内存模型
- Java 内存模型
- Java内存模型
- java内存模型详解
- java内存模型详解
- java内存模型详解
- Java内存模型
- Java内存模型
- 浅谈java内存模型
- JAVA内存模型
- API编程的详细介绍(转)
- java基础:文件编码方式
- VC Afx全局函数、数据类型、字符串转化函数(转)
- Windows API-GDI入门基础知识详解(转)
- VC最常用操作程序20项列举(转)
- java内存模型
- 【SSH】Struts2学习(三)OGNL、OGNL与Struts2结合
- C程序优化
- 详解 CORS 跨域资源共享
- 初级 WINDOWS API C++语言版 编程(转)
- poj 3352(tarjan)
- Windows程序的基本结构(转)
- 杂项汇总-Maven篇(持续集成中...)
- 消息映射的实现(转)