5
5
- JVM
6
6
---
7
7
8
- > 来自[ 说出你的愿望吧丷] ( https://juejin.im/user/5c2400afe51d45451758aa96 ) 投稿,原文地址:https://juejin.im/post/5e1505d0f265da5d5d744050 。
8
+ > 来自[ 说出你的愿望吧丷] ( https://juejin.im/user/5c2400afe51d45451758aa96 ) 投稿,原文地址:< https://juejin.im/post/5e1505d0f265da5d5d744050 > 。
9
9
10
10
## 前言
11
11
@@ -71,12 +71,12 @@ JVM 是 Java Virtual Machine 的缩写,它是一个虚构出来的计算机,
71
71
72
72
执行 main 方法的步骤如下:
73
73
74
- 1 . 编译好 App.java 后得到 App.class 后,执行 App.class,系统会启动一个 JVM 进程,从 classpath 路径中找到一个名为 App.class 的二进制文件,将 App 的类信息加载到运行时数据区的方法区内,这个过程叫做 App 类的加载
75
- 2 . JVM 找到 App 的主程序入口,执行 main 方法
76
- 3 . 这个 main 中的第一条语句为 Student student = new Student("tellUrDream") ,就是让 JVM 创建一个 Student 对象,但是这个时候方法区中是没有 Student 类的信息的,所以 JVM 马上加载 Student 类,把 Student 类的信息放到方法区中
77
- 4 . 加载完 Student 类后,JVM 在堆中为一个新的 Student 实例分配内存,然后调用构造函数初始化 Student 实例,这个 Student 实例持有 ** 指向方法区中的 Student 类的类型信息** 的引用
78
- 5 . 执行 student.sayName();时,JVM 根据 student 的引用找到 student 对象,然后根据 student 对象持有的引用定位到方法区中 student 类的类型信息的方法表,获得 sayName() 的字节码地址。
79
- 6 . 执行 sayName()
74
+ 1 . 编译好 App.java 后得到 App.class 后,执行 App.class,系统会启动一个 JVM 进程,从 classpath 路径中找到一个名为 App.class 的二进制文件,将 App 的类信息加载到运行时数据区的方法区内,这个过程叫做 App 类的加载
75
+ 2 . JVM 找到 App 的主程序入口,执行 main 方法
76
+ 3 . 这个 main 中的第一条语句为 Student student = new Student("tellUrDream") ,就是让 JVM 创建一个 Student 对象,但是这个时候方法区中是没有 Student 类的信息的,所以 JVM 马上加载 Student 类,把 Student 类的信息放到方法区中
77
+ 4 . 加载完 Student 类后,JVM 在堆中为一个新的 Student 实例分配内存,然后调用构造函数初始化 Student 实例,这个 Student 实例持有 ** 指向方法区中的 Student 类的类型信息** 的引用
78
+ 5 . 执行 student.sayName();时,JVM 根据 student 的引用找到 student 对象,然后根据 student 对象持有的引用定位到方法区中 student 类的类型信息的方法表,获得 sayName() 的字节码地址。
79
+ 6 . 执行 sayName()
80
80
81
81
其实也不用管太多,只需要知道对象实例初始化时会去方法区中找类信息,完成后再到栈那里去运行方法。找方法就在方法表中找。
82
82
@@ -90,15 +90,15 @@ JVM 是 Java Virtual Machine 的缩写,它是一个虚构出来的计算机,
90
90
91
91
#### 2.1.1 加载
92
92
93
- 1 . 将 class 文件加载到内存
94
- 2 . 将静态数据结构转化成方法区中运行时的数据结构
95
- 3 . 在堆中生成一个代表这个类的 java.lang.Class 对象作为数据访问的入口
93
+ 1 . 将 class 文件加载到内存
94
+ 2 . 将静态数据结构转化成方法区中运行时的数据结构
95
+ 3 . 在堆中生成一个代表这个类的 java.lang.Class 对象作为数据访问的入口
96
96
97
97
#### 2.1.2 链接
98
98
99
- 1 . 验证:确保加载的类符合 JVM 规范和安全,保证被校验类的方法在运行时不会做出危害虚拟机的事件,其实就是一个安全检查
100
- 2 . 准备:为 static 变量在方法区中分配内存空间,设置变量的初始值,例如 static int a = 3 (注意:准备阶段只设置类中的静态变量(方法区中),不包括实例变量(堆内存中),实例变量是对象初始化时赋值的)
101
- 3 . 解析:虚拟机将常量池内的符号引用替换为直接引用的过程(符号引用比如我现在 import java.util.ArrayList 这就算符号引用,直接引用就是指针或者对象地址,注意引用对象一定是在内存进行)
99
+ 1 . 验证:确保加载的类符合 JVM 规范和安全,保证被校验类的方法在运行时不会做出危害虚拟机的事件,其实就是一个安全检查
100
+ 2 . 准备:为 static 变量在方法区中分配内存空间,设置变量的初始值,例如 static int a = 3 (注意:准备阶段只设置类中的静态变量(方法区中),不包括实例变量(堆内存中),实例变量是对象初始化时赋值的)
101
+ 3 . 解析:虚拟机将常量池内的符号引用替换为直接引用的过程(符号引用比如我现在 import java.util.ArrayList 这就算符号引用,直接引用就是指针或者对象地址,注意引用对象一定是在内存进行)
102
102
103
103
#### 2.1.3 初始化
104
104
@@ -114,10 +114,10 @@ GC 将无用对象从内存中卸载
114
114
115
115
加载一个 Class 类的顺序也是有优先级的,类加载器从最底层开始往上的顺序是这样的
116
116
117
- 1 . BootStrap ClassLoader:rt.jar
118
- 2 . Extension ClassLoader: 加载扩展的 jar 包
119
- 3 . App ClassLoader:指定的 classpath 下面的 jar 包
120
- 4 . Custom ClassLoader:自定义的类加载器
117
+ 1 . BootStrap ClassLoader:rt.jar
118
+ 2 . Extension ClassLoader: 加载扩展的 jar 包
119
+ 3 . App ClassLoader:指定的 classpath 下面的 jar 包
120
+ 4 . Custom ClassLoader:自定义的类加载器
121
121
122
122
### 2.3 双亲委派机制
123
123
@@ -190,7 +190,7 @@ public class Person{
190
190
191
191
局部变量表用于存放方法参数和方法内部所定义的局部变量。它的容量是以 Slot 为最小单位,一个 slot 可以存放 32 位以内的数据类型。
192
192
193
- 虚拟机通过索引定位的方式使用局部变量表,范围为[ 0,局部变量表的 slot 的数量] 。方法中的参数就会按一定顺序排列在这个局部变量表中,至于怎么排的我们可以先不关心。而为了节省栈帧空间,这些 slot 是可以复用的,当方法执行位置超过了某个变量,那么这个变量的 slot 可以被其它变量复用。当然如果需要复用,那我们的垃圾回收自然就不会去动这些内存。
193
+ 虚拟机通过索引定位的方式使用局部变量表,范围为 ` [0,局部变量表的 slot 的数量] ` 。方法中的参数就会按一定顺序排列在这个局部变量表中,至于怎么排的我们可以先不关心。而为了节省栈帧空间,这些 slot 是可以复用的,当方法执行位置超过了某个变量,那么这个变量的 slot 可以被其它变量复用。当然如果需要复用,那我们的垃圾回收自然就不会去动这些内存。
194
194
195
195
#### 3.3.6 虚拟机堆的概念
196
196
@@ -239,11 +239,11 @@ MaxMetaspaceSize:限制元空间大小上限,防止占用过多物理内存
239
239
240
240
(了解一下即可)在 Java 语言汇总能作为 GC Roots 的对象分为以下几种:
241
241
242
- 1 . 虚拟机栈(栈帧中的本地方法表)中引用的对象(局部变量)
243
- 2 . 方法区中静态变量所引用的对象(静态变量)
244
- 3 . 方法区中常量引用的对象
245
- 4 . 本地方法栈(即 native 修饰的方法)中 JNI 引用的对象(JNI 是 Java 虚拟机调用对应的 C 函数的方式,通过 JNI 函数也可以创建新的 Java 对象。且 JNI 对于对象的局部引用或者全局引用都会把它们指向的对象都标记为不可回收)
246
- 5 . 已启动的且未终止的 Java 线程
242
+ 1 . 虚拟机栈(栈帧中的本地方法表)中引用的对象(局部变量)
243
+ 2 . 方法区中静态变量所引用的对象(静态变量)
244
+ 3 . 方法区中常量引用的对象
245
+ 4 . 本地方法栈(即 native 修饰的方法)中 JNI 引用的对象(JNI 是 Java 虚拟机调用对应的 C 函数的方式,通过 JNI 函数也可以创建新的 Java 对象。且 JNI 对于对象的局部引用或者全局引用都会把它们指向的对象都标记为不可回收)
246
+ 5 . 已启动的且未终止的 Java 线程
247
247
248
248
这种方法的优点是能够解决循环引用的问题,可它的实现需要耗费大量资源和时间,也需要 GC(它的分析过程引用关系不能发生变化,所以需要停止所有进程)
249
249
@@ -259,8 +259,8 @@ finalize()是 Object 类的一个方法、一个对象的 finalize()方法只会
259
259
260
260
判断一个对象的死亡至少需要两次标记
261
261
262
- 1 . 如果对象进行可达性分析之后没发现与 GC Roots 相连的引用链,那它将会第一次标记并且进行一次筛选。判断的条件是决定这个对象是否有必要执行 finalize()方法。如果对象有必要执行 finalize()方法,则被放入 F-Queue 队列中。
263
- 2 . GC 对 F-Queue 队列中的对象进行二次标记。如果对象在 finalize()方法中重新与引用链上的任何一个对象建立了关联,那么二次标记时则会将它移出“即将回收”集合。如果此时对象还没成功逃脱,那么只能被回收了。
262
+ 1 . 如果对象进行可达性分析之后没发现与 GC Roots 相连的引用链,那它将会第一次标记并且进行一次筛选。判断的条件是决定这个对象是否有必要执行 finalize()方法。如果对象有必要执行 finalize()方法,则被放入 F-Queue 队列中。
263
+ 2 . GC 对 F-Queue 队列中的对象进行二次标记。如果对象在 finalize()方法中重新与引用链上的任何一个对象建立了关联,那么二次标记时则会将它移出“即将回收”集合。如果此时对象还没成功逃脱,那么只能被回收了。
264
264
265
265
如果确定对象已经死亡,我们又该如何回收这些垃圾呢
266
266
0 commit comments