Skip to content

Commit e42d7b0

Browse files
authored
Update JVM 面试题.md
1 parent 6a8a318 commit e42d7b0

File tree

1 file changed

+101
-17
lines changed

1 file changed

+101
-17
lines changed

docs/JVM 面试题.md

Lines changed: 101 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
## jvm 的主要组成部分及其作用
1+
## JVM 的主要组成部分及其作用
22

33
- 类加载器(ClassLoader)
44
- 运行时数据区(Runtime Data Area)
@@ -7,47 +7,58 @@
77

88
组件的作用: 首先通过类加载器(ClassLoader)会加载类文件到内存,Class loader只管加载,只要符合文件结构就加载。运行时数据区(Runtime Data Area)是jvm的重点,我们所有所写的程序都被加载到这里,之后才开始运行。而字节码文件只是 JVM 的一套指令集规范,并不能直接交个底层操作系统去执行,因此需要特定的命令解析器执行引擎(Execution Engine),将字节码翻译成底层系统指令,再交由 CPU 去执行,而这个过程中需要调用其他语言的本地库接口(Native Interface)来融合不同的语言为java所用,从而实现整个程序的功能。
99

10-
## jvm 运行时数据区
10+
## JVM 运行时数据区
1111

1212
- 程序计数器
1313

1414
指向当前线程执行的字节码指令的地址( 行号 )。 这样做的**用处是多线程操作时, 挂起的线程在重新激活后能够知道上次执行的位置。**
1515

1616
- 虚拟机栈
1717

18-
存储当前线程执行方法时所需要的数据,指令,返回地址。因此一个线程独享一块虚拟机栈 ,栈中内存的单位是栈帧。 每个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法出口信息。
18+
存储当前线程执行方法时所需要的数据,指令,返回地址。因此一个线程独享一块虚拟机栈 ,栈中内存的单位是栈帧。 每个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法出口信息。
1919

2020
- 本地方法栈
2121

22-
本地方法栈为虚拟机使用到的 Native 方法服务。
22+
本地方法栈为虚拟机使用到的 Native 方法服务。
2323

2424
-
2525

26-
详见堆内存模型。
26+
唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。 详见堆内存模型。
2727

2828
- 方法区
2929

30-
用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。方法区是一个 JVM 规范,永久代与元空间都是其一种实现方式。
30+
用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。方法区是一个 JVM 规范,永久代与元空间都是其一种实现方式。
3131

3232
###
3333

3434
堆内存模型:
3535

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)
3737

3838
**此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。**
3939

4040
## 堆和栈的区别
4141

4242
1. 栈内存存储的是局部变量而堆内存存储的是实体;
43-
4443
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+
因为年轻代中的对象基本都是朝生夕死的,所以在年轻代的垃圾回收算法使用的是复制算法,复制算法的基本思想就是将内存分为两块,每次只用其中一块,当这一块内存用完,就将还活着的对象复制到另外一块上面。复制算法不会产生内存碎片。
4551

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”区被填满之后,会将所有对象移动到年老代中
4753

48-
## 什么是双亲委派模型?
54+
## 什么是双亲委派模型?
4955

50-
如果一个类加载器收到了类加载的请求,它首先不会自己去加载这个类,而是把这个请求委派给父类加载器去完成,每一层的类加载器都是如此,这样所有的加载请求都会被传送到顶层的启动类加载器中,只有当父加载无法完成加载请求(它的搜索范围中没找到所需的类)时,子加载器才会尝试去加载类。
56+
双亲委派的意思是如果一个类加载器需要加载类,那么首先它会把这个类请求委派给父类加载器去完成,每一层都是如此。一直递归到顶层,当父加载器无法完成这个请求时,子类才会尝试去加载。
57+
58+
## 双亲委派机制的作用
59+
60+
1、防止重复加载同一个`.class`。通过委托去向上面问一问,加载过了,就不用再加载一遍。保证数据安全。
61+
2、保证核心`.class`不能被篡改。通过委托方式,不会去篡改核心`.class`,不同的加载器加载同一个`.class`也不是同一个`Class`对象。这样保证了`Class`执行安全。
5162

5263
## 对象的创建过程?
5364

@@ -91,12 +102,53 @@
91102
- 弱引用
92103
- 虚引用
93104

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+
95151

96-
- 标记-清除算法
97-
- 标记-整理算法
98-
- 复制算法
99-
- 分代算法
100152

101153
## jvm 有哪些垃圾回收器?
102154

@@ -128,12 +180,30 @@
128180

129181

130182

131-
## 详细介绍一下 CMS 垃圾回收器
183+
## 详细介绍一下 CMS 垃圾回收器
132184

133185
CMS 是英文 Concurrent Mark-Sweep 的简称,是以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器。对于要求服务器响应速度的应用上,这种垃圾回收器非常适合。在启动 JVM 的参数加上“-XX:+UseConcMarkSweepGC”来指定使用 CMS 垃圾回收器。
134186

135187
CMS 使用的是标记-清除的算法实现的,所以在 gc 的时候回产生大量的内存碎片,当剩余内存不能满足程序运行要求时,系统将会出现 Concurrent Mode Failure,临时 CMS 会采用 Serial Old 回收器进行垃圾清除,此时的性能将会被降低。
136188

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+
137207
## 说一下 jvm 调优的工具
138208

139209
JDK 自带了很多监控工具,都位于 JDK 的 bin 目录下,其中最常用的是 jconsole 和 jvisualvm 这两款视图监控工具。
@@ -152,3 +222,17 @@ JDK 自带了很多监控工具,都位于 JDK 的 bin 目录下,其中最常
152222
- -XX:+UseConcMarkSweepGC:指定使用 CMS + Serial Old 垃圾回收器组合;
153223
- -XX:+PrintGC:开启打印 gc 信息;
154224
- -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

Comments
 (0)