77
88> 如果没有特殊说明,都是针对的是 HotSpot 虚拟机。
99>
10- > 本文基于《深入理解Java虚拟机:JVM高级特性与最佳实践 》进行总结补充。
10+ > 本文基于《深入理解 Java 虚拟机:JVM 高级特性与最佳实践 》进行总结补充。
1111>
1212> 常见面试题 :
1313>
@@ -43,6 +43,8 @@ Java 虚拟机在执行 Java 程序的过程中会把它管理的内存划分成
4343- 方法区
4444- 直接内存 (非运行时数据区的一部分)
4545
46+ Java 虚拟机规范对于运行时数据区域的规定是相当宽松的。以堆为例:堆可以是连续空间,也可以不连续。堆的大小可以固定,也可以在运行时按需扩展 。虚拟机实现者可以使用任何垃圾回收算法管理堆,甚至完全不进行垃圾收集也是可以的。
47+
4648### 程序计数器
4749
4850程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。字节码解释器工作时通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等功能都需要依赖这个计数器来完成。
@@ -54,7 +56,7 @@ Java 虚拟机在执行 Java 程序的过程中会把它管理的内存划分成
5456- 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。
5557- 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。
5658
57- ⚠️注意 :程序计数器是唯一一个不会出现 ` OutOfMemoryError ` 的内存区域,它的生命周期随着线程的创建而创建,随着线程的结束而死亡。
59+ ⚠️ 注意 :程序计数器是唯一一个不会出现 ` OutOfMemoryError ` 的内存区域,它的生命周期随着线程的创建而创建,随着线程的结束而死亡。
5860
5961### Java 虚拟机栈
6062
@@ -67,7 +69,7 @@ Java 内存可以粗糙的区分为:
6769
6870栈也就是 Java 虚拟机栈,由一个个栈帧组成,而每个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法出口信息。
6971
70- 局部变量表主要存放了编译期可知的各种数据类型 (boolean、byte、char、short、int、float、long、double)、对象引用(reference 类型,它不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)。
72+ ** 局部变量表 ** 主要存放了编译期可知的各种数据类型 (boolean、byte、char、short、int、float、long、double)、对象引用(reference 类型,它不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)。
7173
7274Java 虚拟机栈会出现两种错误:
7375
@@ -113,9 +115,9 @@ Java 堆是垃圾收集器管理的主要区域,因此也被称作 **GC 堆(
113115
114116下图所示的 Eden 区、两个 Survivor 区 S0 和 S1 都属于新生代,中间一层属于老年代,最下面一层属于永久代。
115117
116- ![ hotspot-heap-structure] ( ./pictures/java内存区域/hotspot-heap-structure.jpg )
118+ ![ hotspot-heap-structure] ( ./pictures/java内存区域/hotspot-heap-structure.png )
117119
118- ** JDK 8 版本之后 PermGen 已被 Metaspace(元空间) 取代,元空间使用的是直接内存。 **
120+ ** JDK 8 版本之后 PermGen(永久) 已被 Metaspace(元空间) 取代,元空间使用的是直接内存** (我会在方法区这部分内容详细介绍到)。
119121
120122大部分情况,对象都会首先在 Eden 区域分配,在一次新生代垃圾回收后,如果对象还存活,则会进入 S0 或者 S1,并且对象的年龄还会加 1(Eden 区->Survivor 区后对象的初始年龄变为 1),当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 ` -XX:MaxTenuringThreshold ` 来设置。
121123
@@ -147,13 +149,15 @@ Java 堆是垃圾收集器管理的主要区域,因此也被称作 **GC 堆(
147149
148150### 方法区
149151
150- 方法区与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然 **Java 虚拟机规范把方法区描述为堆的一个逻辑部分**,但是它却有一个别名叫做 **Non-Heap(非堆)**,目的应该是与 Java 堆区分开来。
152+ 方法区与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的**类信息、字段信息、方法信息、常量、静态变量、即时编译器编译后的代码缓存等数据**。虽然 Java 虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做 **Non-Heap(非堆)**,目的应该是与 Java 堆区分开来。
153+
154+ #### 方法区和永久代以及元空间的关系
151155
152- 方法区也被称为永久代。很多人都会分不清方法区和永久代的关系,为此我也查阅了文献 。
156+ 《Java 虚拟机规范》只是规定了有方法区这么个概念和它的作用,并没有规定如何去实现它。那么,在不同的 JVM 上方法区的实现肯定是不同的了 。
153157
154- #### 方法区和永久代的关系
158+ 方法区和永久代以及元空间的关系很像 Java 中接口和类的关系,类实现了接口,这里的类就可以看作是永久代和元空间,接口可以看作是方法区,也就是说永久代以及元空间是 HotSpot 虚拟机对虚拟机规范中方法区的两种实现方式。
155159
156- > 《Java 虚拟机规范》只是规定了有方法区这么个概念和它的作用,并没有规定如何去实现它。那么,在不同的 JVM 上方法区的实现肯定是不同的了。 **方法区和永久代的关系很像 Java 中接口和类的关系,类实现了接口,而永久代就是 HotSpot 虚拟机对虚拟机规范中方法区的一种实现方式。** 也就是说,永久代是 HotSpot 的概念,方法区是 Java 虚拟机规范中的定义,是一种规范,而永久代是一种实现,一个是标准一个是实现,其他的虚拟机实现并没有永久代这一说法 。
160+ 并且,永久代是 JDK 1.8 之前的方法区实现,JDK 1.8 及以后方法区的实现便成为元空间 。
157161
158162#### 方法区常用参数
159163
@@ -193,19 +197,49 @@ JDK 1.8 的时候,方法区(HotSpot 的永久代)被彻底移除了(JDK1
193197
1941983、在 JDK8,合并 HotSpot 和 JRockit 的代码时, JRockit 从来没有一个叫永久代的东西, 合并之后就没有必要额外的设置这么一个永久代的地方了。
195199
200+ #### 图解 JDK1.6~ JDK1.8 方法区的变化
201+
202+ ![ 方法区-jdk1.6] ( pictures/java内存区域/method-area-1.6.png )
203+
204+ ![ 方法区-jdk1.7] ( pictures/java内存区域/method-area-jdk1.7.png )
205+
206+ ![ 方法区-jdk1.8] ( pictures/java内存区域/method-area-jdk1.8.png )
207+
196208### 运行时常量池
197209
198- 运行时常量池是方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有常量池表(用于存放编译期生成的各种字面量和符号引用)
210+ Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有用于存放编译期生成的各种字面量(Literal)和符号引用(Symbolic Reference)的常量池表(Constant Pool Table)。常量池表会在类加载后存放到方法区的运行时常量池中。
211+
212+ 字面量是源代码中的固定值的表示法,即通过字面我们就能知道其值的含义。字面量包括整数、浮点数和字符串字面量,符号引用包括类符号引用、字段符号引用、方法符号引用和接口方法符号引用。
213+
214+ 运行时常量池的功能类似于传统编程语言的符号表,尽管它包含了比典型符号表更广泛的数据。
199215
200216既然运行时常量池是方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存时会抛出 ` OutOfMemoryError ` 错误。
201217
202218~~ ** JDK1.7 及之后版本的 JVM 已经将运行时常量池从方法区中移了出来,在 Java 堆(Heap)中开辟了一块区域存放运行时常量池。** ~~
203219
204220> ** 🐛 修正(参见:[ issue747] ( https://github.com/Snailclimb/JavaGuide/issues/747 ) ,[ reference] ( https://blog.csdn.net/q5706503/article/details/84640762 ) )** :
205221>
206- > 1 . JDK1.7 之前,运行时常量池包含的字符串常量池和静态变量存放在方法区, 此时 hotspot 虚拟机对方法区的实现为永久代。
207- > 2 . JDK1.7 字符串常量池和静态变量被从方法区拿到了堆中, 这里没有提到运行时常量池,也就是说字符串常量池被单独拿到堆,运行时常量池剩下的东西还在方法区, 也就是 hotspot 中的永久代 。
208- > 3 . JDK1.8 hotspot 移除了永久代用元空间(Metaspace)取而代之, 这时候字符串常量池还在堆, 运行时常量池还在方法区, 只不过方法区的实现从永久代变成了元空间(Metaspace)
222+ > 1 . JDK1.7 之前,运行时常量池包含的字符串常量池和静态变量存放在方法区, 此时 HotSpot 虚拟机对方法区的实现为永久代。
223+ > 2 . JDK1.7 字符串常量池和静态变量被从方法区拿到了堆中, 这里没有提到运行时常量池,也就是说字符串常量池被单独拿到堆,运行时常量池剩下的东西还在方法区, 也就是 HotSpot 中的永久代 。
224+ > 3 . JDK1.8 HotSpot 移除了永久代用元空间(Metaspace)取而代之, 这时候字符串常量池和静态变量还在堆, 运行时常量池还在方法区, 只不过方法区的实现从永久代变成了元空间(Metaspace)
225+
226+ ** 字符串常量池有什么作用?**
227+
228+ ** 字符串常量池** 是 JVM 为了提升性能和减少内存消耗针对字符串(String 类)专门开辟的一块区域,主要目的是为了避免字符串的重复创建。
229+
230+ ``` java
231+ String aa = " ab" ; // 放在常量池中
232+ String bb = " ab" ; // 从常量池中查找
233+ System . out. println(aa== bb);// true
234+ ```
235+
236+ JDK1.7 之前运行时常量池逻辑包含字符串常量池存放在方法区。JDK1.7 的时候,字符串常量池被从方法区拿到了堆中。
237+
238+ 这里的字符串其实就是我们前面提到的字符串字面量。在声明一个字符串字面量时,如果字符串常量池中能够找到该字符串字面量,则直接返回该引用。如果找不到的话,则在常量池中创建该字符串字面量的对象并返回其引用。
239+
240+ ** JDK 1.7 为什么要将字符串常量池移动到堆中?**
241+
242+ 主要是因为永久代(方法区实现)的 GC 回收效率太低,只有在整堆收集 (Full GC)的时候才会被执行 GC。Java 程序中通常会有大量的被创建的字符串等待回收,将字符串常量池放到堆中,能够更高效及时地回收字符串内存。
209243
210244相关问题:[ JVM 常量池中存储的是对象还是引用呢? - RednaxelaFX - 知乎] ( https://www.zhihu.com/question/57109429/answer/151717241 )
211245
@@ -296,8 +330,8 @@ Java 对象的创建过程我建议最好是能默写出来,并且要掌握每
296330## 参考
297331
298332- 《深入理解 Java 虚拟机:JVM 高级特性与最佳实践(第二版》
299- - 《实战 java 虚拟机》
300- - < https://docs.oracle.com/javase/specs/index .html >
333+ - 《自己动手写 Java 虚拟机》
334+ - Chapter 2. The Structure of the Java Virtual Machine: https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2 .html
301335- < http://www.pointsoftware.ch/en/under-the-hood-runtime-data-areas-javas-memory-model/ >
302336- < https://dzone.com/articles/jvm-permgen-%E2%80%93-where-art-thou >
303337- < https://stackoverflow.com/questions/9095748/method-area-and-permgen >
0 commit comments