@@ -60,36 +60,36 @@ Java 虚拟机规范对于运行时数据区域的规定是相当宽松的。以
60
60
61
61
### Java 虚拟机栈
62
62
63
- 与程序计数器一样,Java 虚拟机栈也是线程私有的 ,它的生命周期和线程相同,描述的是 Java 方法执行的内存模型,每次方法调用的数据都是通过栈传递的 。
63
+ 与程序计数器一样,Java 虚拟机栈(后文简称栈)也是线程私有的 ,它的生命周期和线程相同,随着线程的创建而创建,随着线程的死亡而死亡 。
64
64
65
- Java 内存可以粗糙的区分为:
65
+ 栈绝对算的上是 JVM 运行时数据区域的一个核心,除了一些 Native 方法调用是通过本地方法栈实现的(后面会提到),其他所有的 Java 方法调用都是通过栈来实现的(也需要和其他运行时数据区域比如程序计数器配合)。
66
66
67
- - 堆内存(Heap)
68
- - 栈内存 (Stack)
67
+ 方法调用的数据需要通过栈进行传递,每一次方法调用都会有一个对应的栈帧被压入栈中,每一个方法调用结束后,都会有一个栈帧被弹出。
69
68
70
- 栈也就是 Java 虚拟机栈,由一个个栈帧组成,而每个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法出口信息。
69
+ 栈由一个个栈帧组成,而每个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法返回地址。和数据结构上的栈类似,两者都是先进后出的数据结构,只支持出栈和入栈两种操作。
70
+
71
+ ![ 栈] ( pictures/java内存区域/stack.png )
71
72
72
73
** 局部变量表** 主要存放了编译期可知的各种数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference 类型,它不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)。
73
74
74
- Java 虚拟机栈会出现两种错误:
75
+ ** 操作数栈 ** 主要作为方法调用的中转站使用,用于存放方法执行过程中产生的中间计算结果。另外,计算过程中产生的临时变量也会放在操作数栈中。
75
76
76
- - ** ` StackOverFlowError ` :** 若 Java 虚拟机栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前 Java 虚拟机栈的最大深度的时候,就抛出 StackOverFlowError 错误。
77
- - ** ` OutOfMemoryError ` :** Java 虚拟机栈的内存大小可以动态扩展, 如果虚拟机在动态扩展栈时无法申请到足够的内存空间,则抛出` OutOfMemoryError ` 异常。
77
+ ** 动态链接** 主要服务一个方法需要调用其他方法的场景。在 Java 源文件被编译成字节码文件时,所有的变量和方法引用都作为符号引用(Symbilic Reference)保存在Class 文件的常量池里。当一个方法要调用其他方法,需要将常量池中指向方法的符号引用转化为其在内存地址中的直接引用。动态链接的作用就是为了将符号引用转换为调用方法的直接引用。
78
78
79
- ![ ] ( ./pictures/java内存区域/《深入理解虚拟机》第三版的第2章-虚拟机栈 .png)
79
+ ![ ] ( https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/github/javaguide/jvmimage-20220331175738692 .png)
80
80
81
- Java 虚拟机栈也是线程私有的,每个线程都有各自的 Java 虚拟机栈,而且随着线程的创建而创建,随着线程的死亡而死亡 。
81
+ 栈空间虽然不是无限的,但一般正常调用的情况下是不会出现问题的。不过,如果函数调用陷入无限循环的话,就会导致栈中被压入太多栈帧而占用太多空间,导致栈空间过深。那么当线程请求栈的深度超过当前 Java 虚拟机栈的最大深度的时候,就抛出 ` StackOverFlowError ` 错误 。
82
82
83
- 🌈 拓展一下: ** 方法/函数如何调用? **
83
+ Java 方法有两种返回方式,一种是 return 语句正常返回,一种是抛出异常。不管哪种返回方式,都会导致栈帧被弹出。也就是说, ** 栈帧随着方法调用而创建,随着方法结束而销毁。无论方法正常完成还是异常完成都算作方法结束。 **
84
84
85
- Java 栈可以类比数据结构中栈,Java 栈中保存的主要内容是栈帧,每一次函数调用都会有一个对应的栈帧被压入 Java 栈,每一个函数调用结束后,都会有一个栈帧被弹出 。
85
+ 除了 ` StackOverFlowError ` 错误之外,栈还可能会出现 ` OutOfMemoryError ` 错误,这是因为如果栈的内存大小可以动态扩展, 如果虚拟机在动态扩展栈时无法申请到足够的内存空间,则抛出 ` OutOfMemoryError ` 异常 。
86
86
87
- Java 方法有两种返回方式 :
87
+ 简单总结一下程序运行中栈可能会出现两种错误 :
88
88
89
- 1 . return 语句 。
90
- 2 . 抛出异常 。
89
+ - ** ` StackOverFlowError ` : ** 若栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前 Java 虚拟机栈的最大深度的时候,就抛出 ` StackOverFlowError ` 错误 。
90
+ - ** ` OutOfMemoryError ` : ** 如果栈的内存大小可以动态扩展, 如果虚拟机在动态扩展栈时无法申请到足够的内存空间,则抛出 ` OutOfMemoryError ` 异常 。
91
91
92
- 不管哪种返回方式都会导致栈帧被弹出。
92
+ ![ ] ( ./pictures/java内存区域/《深入理解虚拟机》第三版的第2章-虚拟机栈.png )
93
93
94
94
### 本地方法栈
95
95
@@ -149,19 +149,33 @@ Java 堆是垃圾收集器管理的主要区域,因此也被称作 **GC 堆(
149
149
150
150
### 方法区
151
151
152
- 方法区与 Java 堆一样 ,是各个线程共享的内存区域,它用于存储已被虚拟机加载的**类信息、字段信息、方法信息、常量、静态变量、即时编译器编译后的代码缓存等数据**。虽然 Java 虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做 **Non-Heap(非堆)**,目的应该是与 Java 堆区分开来 。
152
+ 方法区属于是 JVM 运行时数据区域的一块逻辑区域 ,是各个线程共享的内存区域。
153
153
154
- #### 方法区和永久代以及元空间的关系
154
+ 《Java 虚拟机规范》只是规定了有方法区这么个概念和它的作用,方法区到底要如何实现那就是虚拟机自己要考虑的事情了。也就是说,在不同的虚拟机实现上,方法区的实现是不同的。
155
155
156
- 《Java 虚拟机规范》只是规定了有方法区这么个概念和它的作用,并没有规定如何去实现它。因此,在不同的虚拟机实现上,方法区的实现是不同的 。
156
+ 当虚拟机要使用一个类时,它需要读取并解析 Class 文件获取相关信息,再将信息存入到方法区。方法区会存储已被虚拟机加载的 **类信息、字段信息、方法信息、常量、静态变量、即时编译器编译后的代码缓存等数据** 。
157
157
158
- 方法区和永久代以及元空间的关系很像 Java 中接口和类的关系,类实现了接口,这里的类就可以看作是永久代和元空间,接口可以看作是方法区,也就是说永久代以及元空间是 HotSpot 虚拟机对虚拟机规范中方法区的两种实现方式。
159
-
160
- 并且,永久代是 JDK 1.8 之前的方法区实现,JDK 1.8 及以后方法区的实现便成为元空间。
158
+ **方法区和永久代以及元空间是什么关系呢?** 方法区和永久代以及元空间的关系很像 Java 中接口和类的关系,类实现了接口,这里的类就可以看作是永久代和元空间,接口可以看作是方法区,也就是说永久代以及元空间是 HotSpot 虚拟机对虚拟机规范中方法区的两种实现方式。并且,永久代是 JDK 1.8 之前的方法区实现,JDK 1.8 及以后方法区的实现便成为元空间。
161
159
162
160

163
161
164
- #### 方法区常用参数
162
+ **为什么要将永久代 (PermGen) 替换为元空间 (MetaSpace) 呢?**
163
+
164
+ 下图来自《深入理解 Java 虚拟机》第 3 版 2.2.5
165
+
166
+ 
167
+
168
+ 1、整个永久代有一个 JVM 本身设置的固定大小上限,无法进行调整,而元空间使用的是直接内存,受本机可用内存的限制,虽然元空间仍旧可能溢出,但是比原来出现的几率会更小。
169
+
170
+ > 当元空间溢出时会得到如下错误: `java.lang.OutOfMemoryError: MetaSpace`
171
+
172
+ 你可以使用 `-XX:MaxMetaspaceSize` 标志设置最大元空间大小,默认值为 unlimited,这意味着它只受系统内存的限制。`-XX:MetaspaceSize` 调整标志定义元空间的初始大小如果未指定此标志,则 Metaspace 将根据运行时的应用程序需求动态地重新调整大小。
173
+
174
+ 2、元空间里面存放的是类的元数据,这样加载多少类的元数据就不由 `MaxPermSize` 控制了, 而由系统的实际可用空间来控制,这样能加载的类就更多了。
175
+
176
+ 3、在 JDK8,合并 HotSpot 和 JRockit 的代码时, JRockit 从来没有一个叫永久代的东西, 合并之后就没有必要额外的设置这么一个永久代的地方了。
177
+
178
+ **方法区常用参数有哪些?**
165
179
166
180
JDK 1.8 之前永久代还没被彻底移除的时候通常通过下面这些参数来调节方法区大小。
167
181
@@ -181,30 +195,20 @@ JDK 1.8 的时候,方法区(HotSpot 的永久代)被彻底移除了(JDK1
181
195
182
196
与永久代很大的不同就是,如果不指定大小的话,随着更多类的创建,虚拟机会耗尽所有可用的系统内存。
183
197
184
- #### 为什么要将永久代 (PermGen) 替换为元空间 (MetaSpace) 呢?
185
-
186
- 下图来自《深入理解 Java 虚拟机》第 3 版 2.2.5
187
-
188
- ![ ] ( https://img-blog.csdnimg.cn/20210425134508117.png )
189
-
190
- 1、整个永久代有一个 JVM 本身设置的固定大小上限,无法进行调整,而元空间使用的是直接内存,受本机可用内存的限制,虽然元空间仍旧可能溢出,但是比原来出现的几率会更小。
191
-
192
- > 当元空间溢出时会得到如下错误: ` java.lang.OutOfMemoryError: MetaSpace `
193
-
194
- 你可以使用 ` -XX:MaxMetaspaceSize ` 标志设置最大元空间大小,默认值为 unlimited,这意味着它只受系统内存的限制。` -XX:MetaspaceSize ` 调整标志定义元空间的初始大小如果未指定此标志,则 Metaspace 将根据运行时的应用程序需求动态地重新调整大小。
195
-
196
- 2、元空间里面存放的是类的元数据,这样加载多少类的元数据就不由 ` MaxPermSize ` 控制了, 而由系统的实际可用空间来控制,这样能加载的类就更多了。
197
-
198
- 3、在 JDK8,合并 HotSpot 和 JRockit 的代码时, JRockit 从来没有一个叫永久代的东西, 合并之后就没有必要额外的设置这么一个永久代的地方了。
199
-
200
- #### 图解 JDK1.6~ JDK1.8 方法区的变化
198
+ 最后,我画了 3 张图带你看看 JDK1.6 到 JDK1.8 方法区的变化。
201
199
202
200
![ 方法区-jdk1.6] ( ./pictures/java内存区域/method-area-1.6.png )
203
201
204
202
![ 方法区-jdk1.7] ( ./pictures/java内存区域/method-area-jdk1.7.png )
205
203
206
204
![ 方法区-jdk1.8] ( ./pictures/java内存区域/method-area-jdk1.8.png )
207
205
206
+ 多提一嘴,为了完善方法区这部分内容的介绍,我看了很多文档,还特意去扒了一下《深入理解Java虚拟机(第3版)》勘误的 issues,简直看到的脑壳疼。。。
207
+
208
+ ![ ] ( https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/github/javaguide/jvmimage-20220331112415166.png )
209
+
210
+ 讲真,深挖下来的话细节太多,也没太大意义(卷不动了),我上面讲到的这些基本就够面试使用了。
211
+
208
212
### 运行时常量池
209
213
210
214
Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有用于存放编译期生成的各种字面量(Literal)和符号引用(Symbolic Reference)的常量池表(Constant Pool Table)。常量池表会在类加载后存放到方法区的运行时常量池中。
@@ -332,6 +336,7 @@ Java 对象的创建过程我建议最好是能默写出来,并且要掌握每
332
336
- 《深入理解 Java 虚拟机:JVM 高级特性与最佳实践(第二版》
333
337
- 《自己动手写 Java 虚拟机》
334
338
- Chapter 2. The Structure of the Java Virtual Machine:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html
339
+ - JVM栈帧内部结构-动态链接:https://chenxitag.com/archives/368
335
340
- < http://www.pointsoftware.ch/en/under-the-hood-runtime-data-areas-javas-memory-model/ >
336
341
- < https://dzone.com/articles/jvm-permgen-%E2%80%93-where-art-thou >
337
342
- < https://stackoverflow.com/questions/9095748/method-area-and-permgen >
0 commit comments