1
1
Java内存模型
2
2
===
3
3
4
- Java虚拟机(Java Virtual Machine)在执行Java程序的过程中,会把它管理的内存划分为五个不同的数据区域(Heap Memory、Stack Memory、Method Area、PC、Native Stack Memory),这些区域都有各自的用途、创建时间、销毁时间:
4
+ Java虚拟机(Java Virtual Machine)在执行Java程序的过程中,会把它管理的内存划分为五个不同的数据区域(Heap Memory、
5
+ Stack Memory、Method Area、PC、Native Stack Memory),这些区域都有各自的用途、创建时间、销毁时间:
5
6
![ ] ( https://raw.githubusercontent.com/CharonChui/Pictures/master/jvm_memory_model.jpeg )
6
7
7
8
## Program Counter Register
8
9
9
- 程序计数器是一块较小的内存空间,严格来说是一个数据结构,用于保存当前正在执行的程序的内存地址,由于Java是支持多线程执行的,所以程序执行的轨迹不可能一直都是线性执行。当有多个线程交叉执行时,被中断的线程的程序当前执行到哪条内存地址必然要保存下来,以便用于被中断的线程恢复执行时再按照被中断时的指令地址继续执行下去。为了线程切换后能恢复到正确的执行位置,每个线程都需要有一个独立的程序计数器,各个线程之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存,这在某种程度上有点类似于“ThreadLocal”,是线程安全的。可以看作是当前线程所执行的字节码的行号指示器。
10
+ 程序计数器是一块较小的内存空间,严格来说是一个数据结构,用于保存当前正在执行的程序的内存地址,由于Java是支持多线程执行的,所以程序执行的轨迹
11
+ 不可能一直都是线性执行。当有多个线程交叉执行时,被中断的线程的程序当前执行到哪条内存地址必然要保存下来,以便用于被中断的线程恢复执行时再按照
12
+ 被中断时的指令地址继续执行下去。为了线程切换后能恢复到正确的执行位置,每个线程都需要有一个独立的程序计数器,各个线程之间计数器互不影响,独立存储,
13
+ 我们称这类内存区域为“线程私有”的内存,这在某种程度上有点类似于“ThreadLocal”,是线程安全的。可以看作是当前线程所执行的字节码的行号指示器。
10
14
11
15
从上面的介绍中我们知道程序计数器主要有两个作用:
12
16
@@ -22,20 +26,19 @@ Java虚拟机(Java Virtual Machine)在执行Java程序的过程中,会把它
22
26
所有的对象实例和数组都存放到Heap内存中。Heap内存也称为共享内存。多线程可以共享这里面的数据。
23
27
24
28
- 堆内存在JVM启动的时候被加载(初始大小: -Xms)
25
-
26
29
- 堆内存在程序运行时会增加或减少
27
-
28
30
- 最小值: -Xmx
29
-
30
- - 从结构上来分,可以分为新生代和老年代。而新生代又可以分为Eden空间、From Survivor空间(s0)、To Survivor空间(s1)。 所有新生成的对象首先都是放在新生代的。需要注意,Survivor的两个区是对称的,没先后关系,所以同一个区中可能同时存在从Eden复制过来的对象,和从前一个Survivor复制过来的对象,而复制到老年代的只有从第一个Survivor区过来的对象。而且,Survivor区总有一个是空的。
31
+ - 从结构上来分,可以分为新生代和老年代。而新生代又可以分为Eden空间、From Survivor空间(s0)、To Survivor空间(s1)。 所有新生成的对象首先
32
+ 都是放在新生代的。需要注意,Survivor的两个区是对称的,没先后关系,所以同一个区中可能同时存在从Eden复制过来的对象,和从前一个Survivor复制
33
+ 过来的对象,而复制到老年代的只有从第一个Survivor区过来的对象。而且,Survivor区总有一个是空的。
31
34
32
35
![ ] ( https://raw.githubusercontent.com/CharonChui/Pictures/master/jvm_heap_memory.png )
33
36
34
37
在并发编程中,多个线程之间采取什么机制进行通信(信息交换),什么机制进行数据的同步?在Java语言中,采用的是共享内存模型来实现多线程之间的信息交换和数据同步的。
35
38
36
39
## Stack Memory
37
40
38
- 栈总是与线程关联在一起的,每当创建一个线程,JVM就会为该线程创建对应的线程栈,线程stack中包含有关线程调用了哪些方法以达到当前执行点的信息。 ,也可以称之为调用栈,只要线程执行代码,调用栈就会发生改变。在这个栈中又会包含多个栈帧(Stack Frame),栈是由一个个栈帧组成,这些栈帧是与每个方法关联起来的,每运行一个方法就创建一个栈帧,每个栈帧会含有一些局部变量表、操作栈和方法返回值等信息。
41
+ 栈总是与线程关联在一起的,每当创建一个线程,JVM就会为该线程创建对应的线程栈,线程stack中包含有关线程调用了哪些方法以达到当前执行点的信息,也可以称之为调用栈,只要线程执行代码,调用栈就会发生改变。在这个栈中又会包含多个栈帧(Stack Frame),栈是由一个个栈帧组成,这些栈帧是与每个方法关联起来的,每运行一个方法就创建一个栈帧,每个栈帧会含有一些局部变量表、操作栈和方法返回值等信息。
39
42
40
43
局部变量表主要存放了编译器可知的各种数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)。
41
44
@@ -51,8 +54,8 @@ Java虚拟机(Java Virtual Machine)在执行Java程序的过程中,会把它
51
54
![ ] ( https://raw.githubusercontent.com/CharonChui/Pictures/master/stack_heap.png )
52
55
53
56
- 局部变量可以是原始类型,在这种情况下,它完全保留在线程堆栈中。
54
- - 局部变量也可以是对对象的引用。在这种情况下,引用(局部变量)存储在线程堆栈中,但是对象本身(如果存储在堆中) 。
55
- - 一个对象可能包含方法,而这些方法可能包含局部变量。即使该方法所属的对象存储在堆内存中,这些局部变量也存储在线程堆内存中 。
57
+ - 局部变量也可以是对对象的引用。在这种情况下,引用(局部变量)存储在线程堆栈中,但是对象本身(如果有)存储在堆中 。
58
+ - 一个对象可能包含方法,而这些方法可能包含局部变量。即使该方法所属的对象存储在堆中,这些局部变量也存储在线程堆栈中 。
56
59
- 对象的成员变量与对象本身一起存储在堆中。不管成员变量是原始类型时,以及它是对对象的引用时,都是如此。
57
60
- 静态类变量也与类定义一起存储在堆中。
58
61
- 引用对象的所有线程都可以访问堆上的对象。当线程可以访问对象时,它也可以访问该对象的成员变量。如果两个线程同时在同一个对象上调用一个方法,则它们都将有权访问该对象的成员变量,但是每个线程将拥有自己的局部变量副本。
@@ -92,7 +95,7 @@ Java 虚拟机栈会出现两种异常:StackOverFlowError 和 OutOfMemoryError
92
95
93
96
JVM会加载、链接、初始化class文件。一个class文件会把其所有所有符号引用都保留在一个位置,即常量池中。
94
97
95
- 每个class文件都会有一个对应constant pool。但是class文件中的常量池显示让不够的 ,因为需要再JVM上执行。这种情况下,需要runtime constant pool来服务JVM的运行。
98
+ 每个class文件都会有一个对应constant pool。但是class文件中的常量池显然是不够的 ,因为需要再JVM上执行。这种情况下,需要runtime constant pool来服务JVM的运行。
96
99
97
100
Java虚拟机加载的每个类或接口都有其常量池的内部版本,称为运行时常量池(runtime constant pool)。运行时常量池是特定于实现的数据结构,它与类文件中的常量池是一一对应映射的。因此,在最初加载类型之后,该类型的所有符号引用都驻留在该类型的运行时常量池中。包括字符串常量,类和接口名称,字段名称以及类中引用的其他常量。
98
101
@@ -109,9 +112,9 @@ System.out.println(str1==str2);//false
109
112
``` java
110
113
String str1 = " str" ;
111
114
String str2 = " ing" ;
112
-
115
+
113
116
String str3 = " str" + " ing" ;// 常量池中的对象
114
- String str4 = str1 + str2; // 在堆上创建的新的对象
117
+ String str4 = str1 + str2; // 在堆上创建的新的对象
115
118
String str5 = " string" ;// 常量池中的对象
116
119
System . out. println(str3 == str4);// false
117
120
System . out. println(str3 == str5);// true
@@ -178,7 +181,7 @@ JMM(Java Memory Model)规定了所有的变量都存储在主内存(Main Memor
178
181
179
182
由上述对JVM内存结构的描述中,我们知道了堆和方法区是线程共享的。而局部变量,方法定义参数和异常处理器参数就不会在线程之间共享,它们不会有内存可见性问题,也不受内存模型的影响。
180
183
181
- Java线程之间的通信由Java内存模型(本文简称为JMM) 控制,JMM决定一个线程对共享变量的写入何时对另一个线程可见。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。
184
+ Java线程之间的通信由Java内存模型(JMM) 控制,JMM决定一个线程对共享变量的写入何时对另一个线程可见。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。
182
185
183
186
假设线程A与线程B之间如要通信的话,必须要经历下面2个步骤:
184
187
@@ -212,7 +215,7 @@ Class Reordering {
212
215
213
216
214
217
215
- 当然不行! 因为在writer方法中,可能发生了重排序,y的写入动作可能发在x写入之前,这种情况下,线程B就有可能看到x的值还是0。
218
+ 当然不行!因为在writer方法中,可能发生了重排序,y的写入动作可能发在x写入之前,这种情况下,线程B就有可能看到x的值还是0。
216
219
217
220
在Java内存模型中,描述了在多线程代码中,哪些行为是正确的、合法的,以及多线程之间如何进行通信,代码中变量的读写行为如何反应到内存、CPU缓存的底层细节。
218
221
@@ -226,7 +229,7 @@ Class Reordering {
226
229
a是在栈内存中的,当main()方法执行结束,a就被清理了。但是你创建的内部类中的方法却可能在main()方法执行完成后再去执行,但是这时候局部变量已经不存在了,那怎么解决这个问题呢?
227
230
因此实际上是在访问它的副本,而不是访问原始的局部变量。
228
231
229
- 在Java的参数传递中,当基本类型作为参数传递时,传递的是值的拷贝,无论你怎么改变这个拷贝,原值是不会改变的;当对象作为参数传递时,传递的是对象的引用的拷贝,无论你怎么改变这个新的引用的指向,原来的引用是不会改变的(当然如果你通过这个引用改变了对象的内容,那么改变实实在在发生了)。知识点三,当final修饰基本类型变量时 ,不可更改其值,当final修饰引用变量时,不可更改其指向,只能更改其对象的内容。
232
+ 在Java的参数传递中,当基本类型作为参数传递时,传递的是值的拷贝,无论你怎么改变这个拷贝,原值是不会改变的;当对象作为参数传递时,传递的是对象的引用的拷贝,无论你怎么改变这个新的引用的指向,原来的引用是不会改变的(当然如果你通过这个引用改变了对象的内容,那么改变实实在在发生了)。而当final修饰基本类型变量时 ,不可更改其值,当final修饰引用变量时,不可更改其指向,只能更改其对象的内容。
230
233
231
234
在Java中内部类会持有外部类的引用和方法中参数的引用,当反编译class文件后,内部类的class文件的构造函数参数中会传入外部类的对象以及方法内局部变量,不管是基本数据类型还是引用变量,如果重新赋值了,会导致内外指向的对象不一致,所以java就暴力的规定使用final,不能重新赋值。
232
235
所以用final修饰实际上就是为了变量值(数据)的一致性。 这里所说的数据一致性,对引用变量来说是引用地址的一致性,对基本类型来说就是值的一致性。
@@ -247,9 +250,9 @@ a是在栈内存中的,当main()方法执行结束,a就被清理了。但是
247
250
- [ Where Has the Java PermGen Gone?] ( https://www.infoq.com/articles/Java-PERMGEN-Removed/ )
248
251
249
252
250
-
253
+
251
254
---
252
255
- 邮箱 :charon.chui@gmail.com
253
256
- Good Luck!
254
257
255
-
258
+
0 commit comments