1
- ## jvm 的主要组成部分及其作用
1
+ ## JVM 的主要组成部分及其作用
2
2
3
3
- 类加载器(ClassLoader)
4
4
- 运行时数据区(Runtime Data Area)
7
7
8
8
组件的作用: 首先通过类加载器(ClassLoader)会加载类文件到内存,Class loader只管加载,只要符合文件结构就加载。运行时数据区(Runtime Data Area)是jvm的重点,我们所有所写的程序都被加载到这里,之后才开始运行。而字节码文件只是 JVM 的一套指令集规范,并不能直接交个底层操作系统去执行,因此需要特定的命令解析器执行引擎(Execution Engine),将字节码翻译成底层系统指令,再交由 CPU 去执行,而这个过程中需要调用其他语言的本地库接口(Native Interface)来融合不同的语言为java所用,从而实现整个程序的功能。
9
9
10
- ## jvm 运行时数据区
10
+ ## JVM 运行时数据区⭐
11
11
12
12
- 程序计数器
13
13
14
14
指向当前线程执行的字节码指令的地址( 行号 )。 这样做的** 用处是多线程操作时, 挂起的线程在重新激活后能够知道上次执行的位置。**
15
15
16
16
- 虚拟机栈
17
17
18
- 存储当前线程执行方法时所需要的数据,指令,返回地址。因此一个线程独享一块虚拟机栈 ,栈中内存的单位是栈帧。 每个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法出口信息。
18
+ 存储当前线程执行方法时所需要的数据,指令,返回地址。因此一个线程独享一块虚拟机栈 ,栈中内存的单位是栈帧。 每个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法出口信息。
19
19
20
20
- 本地方法栈
21
21
22
- 本地方法栈为虚拟机使用到的 Native 方法服务。
22
+ 本地方法栈为虚拟机使用到的 Native 方法服务。
23
23
24
24
- 堆
25
25
26
- 详见堆内存模型。
26
+ 唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。 详见堆内存模型。
27
27
28
28
- 方法区
29
29
30
- 用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。方法区是一个 JVM 规范,永久代与元空间都是其一种实现方式。
30
+ 用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。方法区是一个 JVM 规范,永久代与元空间都是其一种实现方式。
31
31
32
32
### 堆
33
33
34
34
堆内存模型:
35
35
36
- ![ image-20200227234850635] ( https://github.com/lvminghui/Java-Notes/blob/master/docs/imgs/JVM .png)
36
+ ![ image-20200227234850635] ( C:\Users\吕明辉\AppData\Roaming\Typora\typora-user-images\image-20200227234850635 .png)
37
37
38
38
** 此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。**
39
39
40
40
## 堆和栈的区别
41
41
42
42
1 . 栈内存存储的是局部变量而堆内存存储的是实体;
43
-
44
43
2 . 栈内存的更新速度要快于堆内存,因为局部变量的生命周期很短;
44
+ 3 . 栈内存存放的变量生命周期一旦结束就会被释放,而堆内存存放的实体会被垃圾回收机制不定时的回收。、
45
+
46
+ ### 分代回收
47
+
48
+ HotSpot JVM把年轻代分为了三部分:1个Eden区和2个Survivor区(分别叫from和to)。一般情况下,新创建的对象都会被分配到Eden区(一些大对象特殊处理),这些对象经过第一次Minor GC后,如果仍然存活,将会被移到Survivor区。对象在Survivor区中每熬过一次Minor GC,年龄就会增加1岁,当它的年龄增加到一定程度时,就会被移动到年老代中。
49
+
50
+ 因为年轻代中的对象基本都是朝生夕死的,所以在年轻代的垃圾回收算法使用的是复制算法,复制算法的基本思想就是将内存分为两块,每次只用其中一块,当这一块内存用完,就将还活着的对象复制到另外一块上面。复制算法不会产生内存碎片。
45
51
46
- 3 . 栈内存存放的变量生命周期一旦结束就会被释放,而堆内存存放的实体会被垃圾回收机制不定时的回收 。
52
+ 在GC开始的时候,对象只会存在于Eden区和名为“From”的Survivor区,Survivor区“To”是空的。紧接着进行GC,Eden区中所有存活的对象都会被复制到“To”,而在“From”区中,仍存活的对象会根据他们的年龄值来决定去向。年龄达到一定值(年龄阈值,可以通过-XX : MaxTenuringThreshold 来设置)的对象会被移动到年老代中,没有达到阈值的对象会被复制到“To”区域。经过这次GC后,Eden区和From区已经被清空。这个时候,“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎样,都会保证名为To的Survivor区域是空的。Minor GC会一直重复这样的过程,直到“To”区被填满,“To”区被填满之后,会将所有对象移动到年老代中 。
47
53
48
- ## 什么是双亲委派模型?
54
+ ## 什么是双亲委派模型?⭐
49
55
50
- 如果一个类加载器收到了类加载的请求,它首先不会自己去加载这个类,而是把这个请求委派给父类加载器去完成,每一层的类加载器都是如此,这样所有的加载请求都会被传送到顶层的启动类加载器中,只有当父加载无法完成加载请求(它的搜索范围中没找到所需的类)时,子加载器才会尝试去加载类。
56
+ 双亲委派的意思是如果一个类加载器需要加载类,那么首先它会把这个类请求委派给父类加载器去完成,每一层都是如此。一直递归到顶层,当父加载器无法完成这个请求时,子类才会尝试去加载。
57
+
58
+ ## 双亲委派机制的作用
59
+
60
+ 1、防止重复加载同一个` .class ` 。通过委托去向上面问一问,加载过了,就不用再加载一遍。保证数据安全。
61
+ 2、保证核心` .class ` 不能被篡改。通过委托方式,不会去篡改核心` .class ` ,不同的加载器加载同一个` .class ` 也不是同一个` Class ` 对象。这样保证了` Class ` 执行安全。
51
62
52
63
## 对象的创建过程?
53
64
91
102
- 弱引用
92
103
- 虚引用
93
104
94
- ## jvm 有哪些垃圾回收算法
105
+ ## 常见的垃圾回收机制⭐
106
+
107
+ 1 . 引用计数法:引用计数法是一种简单但速度很慢的垃圾回收技术。每个对象都含有一个引用计数器,当有引用连接至对象时,引用计数加1。当引用离开作用域或被置为null时,引用计数减1。虽然管理引用计数的开销不大,但这项开销在整个程序生命周期中将持续发生。垃圾回收器会在含有全部对象的列表上遍历,当发现某个对象引用计数为0时,就释放其占用的空间。
108
+ 2 . 可达性分析算法:这个算法的基本思路就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连(用图论的话来说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。
109
+
110
+
111
+
112
+ ## jvm 有哪些垃圾回收算法⭐
113
+
114
+ - 停止-复制:先暂停程序的运行,然后将所有存活的对象从当前堆复制到另一个堆,没有被复制的对象全部都是垃圾。当对象被复制到新堆时,它们是一个挨着一个的,所以新堆保持紧凑排列,然后就可以按前述方法简单,直接的分配了。缺点是一浪费空间,两个堆之间要来回倒腾,二是当程序进入稳定态时,可能只会产生极少的垃圾,甚至不产生垃圾,尽管如此,复制式回收器仍会将所有内存自一处复制到另一处。
115
+ - 标记-清除:同样是从堆栈和静态存储区出发,遍历所有的引用,进而找出所有存活的对象。每当它找到一个存活的对象,就会给对象一个标记,这个过程中不会回收任何对象。只有全部标记工作完成的时候,清理动作才会开始。在清理过程中,没有标记的对象会被释放,不会发生任何复制动作。所以剩下的堆空间是不连续的,垃圾回收器如果要希望得到连续空间的话,就得重新整理剩下的对象。
116
+ - 标记-整理:它的第一个阶段与标记/清除算法是一模一样的,均是遍历GC Roots,然后将存活的对象标记。移动所有存活的对象,且按照内存地址次序依次排列,然后将末端内存地址以后的内存全部回收。因此,第二阶段才称为整理阶段。
117
+ - 分代收集算法:把Java堆分为新生代和老年代,然后根据各个年代的特点采用最合适的收集算法。新生代中,对象的存活率比较低,所以选用复制算法,老年代中对象存活率高且没有额外空间对它进行分配担保,所以使用“标记-清除”或“标记-整理”算法进行回收。
118
+
119
+ ### Minor GC和Full GC触发条件⭐
120
+
121
+ - Minor GC触发条件:当Eden区满时,触发Minor GC。
122
+ - Full GC触发条件:
123
+ 1 . 调用System.gc时,系统建议执行Full GC,但是不必然执行
124
+ 2 . 老年代空间不足
125
+ 3 . 方法区空间不足
126
+ 4 . 通过Minor GC后进入老年代的平均大小大于老年代的可用内存
127
+ 5 . 由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小
128
+
129
+ ### GC中Stop the world(STW)⭐
130
+
131
+ 在执行垃圾收集算法时,Java应用程序的其他所有除了垃圾收集收集器线程之外的线程都被挂起。此时,系统只能允许GC线程进行运行,其他线程则会全部暂停,等待GC线程执行完毕后才能再次运行。这些工作都是由虚拟机在后台自动发起和自动完成的,是在用户不可见的情况下把用户正常工作的线程全部停下来,这对于很多的应用程序,尤其是那些对于实时性要求很高的程序来说是难以接受的。
132
+
133
+ 但不是说GC必须STW,你也可以选择降低运行速度但是可以并发执行的收集算法,这取决于你的业务。
134
+
135
+ ### 哪些对象可以作为GC Roots
136
+
137
+ 1 . 虚拟机栈(栈帧中的本地变量表)中引用的对象。
138
+ 2 . 方法区中类静态属性引用的对象。
139
+ 3 . 方法区中常量引用的对象。
140
+ 4 . 本地方法栈中JNI(即一般说的Native方法)引用的对象。
141
+
142
+ ### JVM锁优化和膨胀过程⭐
143
+
144
+ 1 . 自旋锁:自旋锁其实就是在拿锁时发现已经有线程拿了锁,自己如果去拿会阻塞自己,这个时候会选择进行一次忙循环尝试。也就是不停循环看是否能等到上个线程自己释放锁。自适应自旋锁指的是例如第一次设置最多自旋10次,结果在自旋的过程中成功获得了锁,那么下一次就可以设置成最多自旋20次。
145
+ 2 . 锁粗化:虚拟机通过适当扩大加锁的范围以避免频繁的拿锁释放锁的过程。
146
+ 3 . 锁消除:通过逃逸分析发现其实根本就没有别的线程产生竞争的可能(别的线程没有临界量的引用),或者同步块内进行的是原子操作,而“自作多情”地给自己加上了锁。有可能虚拟机会直接去掉这个锁。
147
+ 4 . 偏向锁:在大多数的情况下,锁不仅不存在多线程的竞争,而且总是由同一个线程获得。因此为了让线程获得锁的代价更低引入了偏向锁的概念。偏向锁的意思是如果一个线程获得了一个偏向锁,如果在接下来的一段时间中没有其他线程来竞争锁,那么持有偏向锁的线程再次进入或者退出同一个同步代码块,不需要再次进行抢占锁和释放锁的操作。
148
+ 5 . 轻量级锁:当存在超过一个线程在竞争同一个同步代码块时,会发生偏向锁的撤销。当前线程会尝试使用CAS来获取锁,当自旋超过指定次数(可以自定义)时仍然无法获得锁,此时锁会膨胀升级为重量级锁。
149
+ 6 . 重量级锁:重量级锁依赖对象内部的monitor锁来实现,而monitor又依赖操作系统的MutexLock(互斥锁)。当系统检查到是重量级锁之后,会把等待想要获取锁的线程阻塞,被阻塞的线程不会消耗CPU,但是阻塞或者唤醒一个线程,都需要通过操作系统来实现。
150
+
95
151
96
- - 标记-清除算法
97
- - 标记-整理算法
98
- - 复制算法
99
- - 分代算法
100
152
101
153
## jvm 有哪些垃圾回收器?
102
154
128
180
129
181
130
182
131
- ## 详细介绍一下 CMS 垃圾回收器
183
+ ## 详细介绍一下 CMS 垃圾回收器⭐
132
184
133
185
CMS 是英文 Concurrent Mark-Sweep 的简称,是以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器。对于要求服务器响应速度的应用上,这种垃圾回收器非常适合。在启动 JVM 的参数加上“-XX:+UseConcMarkSweepGC”来指定使用 CMS 垃圾回收器。
134
186
135
187
CMS 使用的是标记-清除的算法实现的,所以在 gc 的时候回产生大量的内存碎片,当剩余内存不能满足程序运行要求时,系统将会出现 Concurrent Mode Failure,临时 CMS 会采用 Serial Old 回收器进行垃圾清除,此时的性能将会被降低。
136
188
189
+ ### G1和CMS的比较
190
+
191
+ 1 . CMS收集器是获取最短回收停顿时间为目标的收集器,因为CMS工作时,GC工作线程与用户线程可以并发执行,以此来达到降低手机停顿时间的目的(只有初始标记和重新标记会STW)。但是CMS收集器对CPU资源非常敏感。在并发阶段,虽然不会导致用户线程停顿,但是会占用CPU资源而导致引用程序变慢,总吞吐量下降。
192
+ 2 . CMS仅作用于老年代,是基于标记清除算法,所以清理的过程中会有大量的空间碎片。
193
+ 3 . CMS收集器无法处理浮动垃圾,由于CMS并发清理阶段用户线程还在运行,伴随程序的运行自热会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法在本次收集中处理它们,只好留待下一次GC时将其清理掉。
194
+ 4 . G1是一款面向服务端应用的垃圾收集器,适用于多核处理器、大内存容量的服务端系统。G1能充分利用CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短STW的停顿时间,它满足短时间停顿的同时达到一个高的吞吐量。
195
+ 5 . 从JDK 9开始,G1成为默认的垃圾回收器。当应用有以下任何一种特性时非常适合用G1:Full GC持续时间太长或者太频繁;对象的创建速率和存活率变动很大;应用不希望停顿时间长(长于0.5s甚至1s)。
196
+ 6 . G1将空间划分成很多块(Region),然后他们各自进行回收。堆比较大的时候可以采用,采用复制算法,碎片化问题不严重。整体上看属于标记整理算法,局部(region之间)属于复制算法。
197
+ 7 . G1 需要记忆集 (具体来说是卡表)来记录新生代和老年代之间的引用关系,这种数据结构在 G1 中需要占用大量的内存,可能达到整个堆内存容量的 20% 甚至更多。而且 G1 中维护记忆集的成本较高,带来了更高的执行负载,影响效率。所以 CMS 在小内存应用上的表现要优于 G1,而大内存应用上 G1 更有优势,大小内存的界限是6GB到8GB。
198
+
199
+ ### i++操作的字节码指令⭐
200
+
201
+ 1 . 将int类型常量加载到操作数栈顶
202
+ 2 . 将int类型数值从操作数栈顶取出,并存储到到局部变量表的第1个Slot中
203
+ 3 . 将int类型变量从局部变量表的第1个Slot中取出,并放到操作数栈顶
204
+ 4 . 将局部变量表的第1个Slot中的int类型变量加1
205
+ 5 . 表示将int类型数值从操作数栈顶取出,并存储到到局部变量表的第1个Slot中,即i中
206
+
137
207
## 说一下 jvm 调优的工具
138
208
139
209
JDK 自带了很多监控工具,都位于 JDK 的 bin 目录下,其中最常用的是 jconsole 和 jvisualvm 这两款视图监控工具。
@@ -152,3 +222,17 @@ JDK 自带了很多监控工具,都位于 JDK 的 bin 目录下,其中最常
152
222
- -XX:+UseConcMarkSweepGC:指定使用 CMS + Serial Old 垃圾回收器组合;
153
223
- -XX:+PrintGC:开启打印 gc 信息;
154
224
- -XX:+PrintGCDetails:打印 gc 详细信息。
225
+
226
+ ## TODO:
227
+
228
+ - CMS GC回收分为哪几个阶段?分别做了什么事情?
229
+ - CMS有哪些重要参数?
230
+ - Concurrent Model Failure和ParNew promotion failed什么情况下会发生?
231
+ - CMS的优缺点?
232
+ - 有做过哪些GC调优?
233
+ - 为什么要划分成年轻代和老年代?
234
+ - 年轻代为什么被划分成eden、survivor区域?
235
+ - 年轻代为什么采用的是复制算法?
236
+ - 老年代为什么采用的是标记清除、标记整理算法
237
+ - 什么情况下使用堆外内存?要注意些什么?
238
+ - 堆外内存如何被回收?
0 commit comments