前言
2023-06-19 18:49:33
出自B站 灰灰的Java面试
枫叶云链接:http://cloud.fynote.com/s/4976
JVM面试题大全 Lecturer :严镇涛
1.为什么需要JVM,不要JVM可以吗?
1.JVM可以帮助我们屏蔽底层的操作系统 一次编译,到处运行
2.JVM可以运行Class文件

2.JDK,JRE以及JVM的关系

3.我们的编译器到底干了什么事?
仅仅是将我们的 .java 文件转换成了 .class 文件,实际上就是文件格式的转换,对等信息转换。

4.类加载机制

类加载机制其实就是虚拟机把Class文件加载到内存,并对数据进行校验,转换解析和初始化,形成可以虚拟机直接使用的Java类型,即java.lang.Class。
1.装载
Class文件 -- >二进制字节流 -->类加载器
1)通过一个类的全限定名获取这个类的二进制字节流
2)将这个字节流所代表的静态存储结构转换为方法区的运行时数据结构
3)在java堆中生成一个代表这个类的java.lang.Class对象,做为我们方法区的数据访问入口
2.链接:
1)验证:保证我们加载的类的正确性
- 文件格式验证
- 元数据验证
- 字节码验证
- 符号引用验证
2)准备
为类的静态变量分配内存,并将其初始化为当前类型的默认值。
private static int a = 1 ; 那么他在准备这个阶段 a = 0;
3)解析
解析是从运行时常量池中的符号引用动态确定具体值的过程。
把类中的符号引用转换成直接引用
3.初始化
执行到Clinit方法,为静态变量赋值,初始化静态代码块,初始化当前类的父类
5.类加载器的层次

6.双亲委派机制
父类委托机制
源码 String 自己写 String
java
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}

7.如何打破双亲委派
1.复写
2.SPI Service Provider Interface 服务提供接口 日志 Xml解析 JBDC
可拔插设计 可以随时替换实现
3.OSGI 热部署 热更新
8.运行时数据区

1.方法区 线程共享 方法区是逻辑上堆的一部分 所以他有个名字:非堆
运行时常量池、字段和方法数据,以及方法和构造函数的代码,包括类和实例初始化和接口初始化中使用 的特殊方法
如果方法区域中的内存无法满足分配请求,Java 虚拟机将抛出一个 OutOfMemoryError
2.堆 线程共享 堆是为所有类实例和数组分配内存的运行时数据区域 内存不足 OutOfMemoryError
3.java虚拟机栈 执行java方法的 线程私有的 StackOverflowError
4.本地方法栈 执行本地方法 线程私有 StackOverflowError
5.程序计数器 记录程序执行到的位置 线程私有
9.栈帧结构是什么样子的?

附加信息:栈帧的高度,虚拟机版本信息
栈帧信息:附加信息+动态链接+方法的返回地址
局部变量表:方法中定义的局部变量以及方法的参数都会存放在这张表中 ,单纯的存储单元
操作数栈 以压栈以及出栈的方式存储操作数
举例:
int a = 1;
int b = 1;
int c = a + b ;
方法的返回地址:当你一个方法执行的时候,只有两种方式可以推出
1.遇到方法的返回的字节码指令
2.出现了异常,有异常处理器,则交给异常处理器 ,没有呢?抛异常
10.动态链接
动态链接是为了支持方法的动态调用过程 。
动态链接将这些符号方法引用转换为具体的方法引用
符号引用转变为直接引用 为了支持java的多态
void a(){
b();
}
void b(){
c();
}
void c(){
}
11.java堆为什么要进行分代设计
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uMBO7nGP-1687171635915)(file:///C:\\Users\\root\\AppData\\Local\\Temp\\ksohtml\\wpsE144.tmp.jpg)\] 新老年代划分  Eden区与S区 ### 12.\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NYW5rLRd-1687171635917)(file:///C:\\Users\\root\\AppData\\Local\\Temp\\ksohtml\\wps7F2D.tmp.jpg)\]老年代的担保机制 ### 13.为什么Eden:S0:S1 是8:1:1\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s7CYE1t9-1687171635918)(file:///C:\\Users\\root\\AppData\\Local\\Temp\\ksohtml\\wps942D.tmp.jpg)
98%的对象朝生夕死
14.对象的创建过程

15.方法区与元数据区以及持久代到底是什么关系
方法区 JVM规范
落地:JDK1.7之前 持久代 Perm Space JVM虚拟机自己的内存
JDK1.8之后 元数据区 / 元空间 MetaSpace 直接内存

16.什么时候才会进行垃圾回收
GC是由JVM自动完成的,根据JVM系统环境而定,所以时机是不确定的。
当然,我们可以手动进行垃圾回收,比如调用System.gc()方法通知JVM进行一次垃圾回收,但是
具体什么时刻运行也无法控制。也就是说System.gc()只是通知要回收,什么时候回收由JVM决
定。但是不建议手动调用该方法,因为GC消耗的资源比较大。
(1)当Eden区或者S区不够用了
(2)老年代空间不够用了
(3)方法区空间不够用了
(4)System.gc() //通知 时机也不确定 执行的Full GC
17. 如何确定一个对象是垃圾?
要想进行垃圾回收,得先知道什么样的对象是垃圾。
- 引用计数法
对于某个对象而言,只要应用程序中持有该对象的引用,就说明该对象不是垃圾,如果一个对象没有任何指针对其引用,它就是垃圾。
弊端
:如果AB相互持有引用,导致永远不能被回收。 循环引用 内存泄露 -->内存溢出

- 可达性分析/根搜索算法
通过GC Root的对象,开始向下寻找,看某个对象是否可达
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Oee0Mcut-1687171635921)(file://E:/%E6%A1%8C%E9%9D%A2/yzt/%E7%AC%94%E8%AE%B0%E8%AF%BE%E4%BB%B6/JVM/3%E5%A4%A9JVM%E8%AE%AD%E7%BB%83%E8%90%A5/%E8%B5%84%E6%96%99+%E7%AC%94%E8%AE%B0/images/64.png?lastModify=1646659177)\]
> **能作为GC Root:类加载器、Thread、虚拟机栈的本地变量表、static成员、常量引用、本地方法栈的变量等。**
虚拟机栈(栈帧中的本地变量表)中引用的对象。
方法区中类静态属性引用的对象。
方法区中常量引用的对象。
本地方法栈中JNI(即一般说的Native方法)引用的对象。
### 18.对象被判定为不可达对象之后就"死"了吗

### 垃圾收集算法
> **已经能够确定一个对象为垃圾之后,接下来要考虑的就是回收,怎么回收呢?得要有对应的算法,下面介绍常见的垃圾回收算法。高效 健壮**
##### 标记-清除(Mark-Sweep)
* **标记**
**找出内存中需要回收的对象,并且把它们标记出来**
> **此时堆中所有的对象都会被扫描一遍,从而才能确定需要回收的对象,比较耗时**
\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IuL0BB27-1687171635922)(file://E:\\桌面\\yzt\\笔记课件\\JVM\\3天JVM训练营\\资料+笔记\\images\\25.png?lastModify=1646720640)\]
* **清除**
**清除掉被标记需要回收的对象,释放出对应的内存空间**
\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-roEivuWw-1687171635924)(file://E:\\桌面\\yzt\\笔记课件\\JVM\\3天JVM训练营\\资料+笔记\\images\\26.png?lastModify=1646720640)\]
`缺点`
标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程
序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
(1)标记和清除两个过程都比较耗时,效率不高
(2)会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
##### 标记-复制(Mark-Copying)
**将内存划分为两块相等的区域,每次只使用其中一块,如下图所示:**
\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pyg6A15O-1687171635925)(file://E:\\桌面\\yzt\\笔记课件\\JVM\\3天JVM训练营\\资料+笔记\\images\\27.png?lastModify=1646720640)\]
**当其中一块内存使用完了,就将还存活的对象复制到另外一块上面,然后把已经使用过的内存空间一次清除掉。**
\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LTJ91xFA-1687171635926)(file://E:\\桌面\\yzt\\笔记课件\\JVM\\3天JVM训练营\\资料+笔记\\images\\28.png?lastModify=1646720640)\]
`缺点:`空间利用率降低。
##### 标记-整理(Mark-Compact)
> **复制收集算法在对象存活率较高时就要进行较多的复制操作,效率将会变低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都有100%存活的极端情况,所以老年代一般不能直接选用这种算法。**
**标记过程仍然与"标记-清除"算法一样,但是后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。**
> **其实上述过程相对"复制算法"来讲,少了一个"保留区"**
\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tnNGnHQX-1687171635928)(file://E:\\桌面\\yzt\\笔记课件\\JVM\\3天JVM训练营\\资料+笔记\\images\\25.png?lastModify=1646720640)\]
**让所有存活的对象都向一端移动,清理掉边界意外的内存。**
\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UVCyLkZr-1687171635929)(file://E:\\桌面\\yzt\\笔记课件\\JVM\\3天JVM训练营\\资料+笔记\\images\\29.png?lastModify=1646720640)\]
#### 分代收集算法
> **既然上面介绍了3中垃圾收集算法,那么在堆内存中到底用哪一个呢?**
**Young区:复制算法(对象在被分配之后,可能生命周期比较短,Young区复制效率比较高)**
**Old区:标记清除或标记整理(Old区对象存活时间比较长,复制来复制去没必要,不如做个标记再清理)**
### 垃圾收集器
> **如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。**
\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UaJ6u0MM-1687171635931)(file://E:\\桌面\\yzt\\笔记课件\\JVM\\3天JVM训练营\\资料+笔记\\images\\30.png?lastModify=1646736013)\]
* Serial
**Serial收集器是最基本、发展历史最悠久的收集器,曾经(在JDK1.3.1之前)是虚拟机新生代收集的唯一选择。**
**它是一种单线程收集器,不仅仅意味着它只会使用一个CPU或者一条收集线程去完成垃圾收集工作,更重要的是其在进行垃圾收集的时候需要暂停其他线程。**
优点:简单高效,拥有很高的单线程收集效率
缺点:收集过程需要暂停所有线程
算法:复制算法
适用范围:新生代
应用:Client模式下的默认新生代收集器
\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2RFO6GgQ-1687171635932)(file://E:\\桌面\\yzt\\笔记课件\\JVM\\3天JVM训练营\\资料+笔记\\images\\31.png?lastModify=1646736013)\]
* Serial Old
Serial Old收集器是Serial收集器的老年代版本,也是一个单线程收集器,不同的是采用"**标记-整理算法**",运行过程和Serial收集器一样。
\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-grvtJfYu-1687171635934)(file://E:\\桌面\\yzt\\笔记课件\\JVM\\3天JVM训练营\\资料+笔记\\images\\32.png?lastModify=1646736013)\]
* ParNew
**可以把这个收集器理解为Serial收集器的多线程版本。**
优点:在多CPU时,比Serial效率高。
缺点:收集过程暂停所有应用程序线程,单CPU时比Serial效率差。
算法:复制算法
适用范围:新生代
应用:运行在Server模式下的虚拟机中首选的新生代收集器
\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yBlAzkgL-1687171635935)(file://E:\\桌面\\yzt\\笔记课件\\JVM\\3天JVM训练营\\资料+笔记\\images\\33.png?lastModify=1646736013)\]
* Parallel Scavenge
**Parallel Scavenge收集器是一个新生代收集器,它也是使用复制算法的收集器,又是并行的多线程收集器,看上去和ParNew一样,但是Parallel Scanvenge更关注系统的吞吐量**。
> **吞吐量=运行用户代码的时间/(运行用户代码的时间+垃圾收集时间)**
>
> **比如虚拟机总共运行了100分钟,垃圾收集时间用了1分钟,吞吐量=(100-1)/100=99%。**
>
> **若吞吐量越大,意味着垃圾收集的时间越短,则用户代码可以充分利用CPU资源,尽快完成程序的运算任务。**
-XX:MaxGCPauseMillis控制最大的垃圾收集停顿时间,
-XX:GCRatio直接设置吞吐量的大小。
* Parallel Old
**Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用多线程和标记-整理算法** 进行垃圾回收,也是更加关注系统的**吞吐量**。
* CMS
> `官网`: