【Java面试】JVM汇总
目录
1.JVM为什么能跨平台?
2.JVM由哪些部分构成?每个部分起到什么作用?
3.什么是双亲委派?双亲委派的两大作用是什么?
举个例子🌰:
为什么要有这种“家族规矩”?
破坏双亲委派的场景(进阶了解)
4.Tomcat为什么要自定义类加载器?
5.程序计数器的作用是什么?
6.虚拟机栈(Java方法栈、JVM栈)的作用是什么?
7.堆的作用是什么?
8.GC如何判断对象是否可回收?
9.垃圾回收算法有哪些?
10.常见的垃圾收集器有哪些?
1.JVM为什么能跨平台?
Java 代码被编译成与操作系统无关的字节码,而 JVM 会将这些字节码解释为机器码,最终实现“一次编译,到处运行”——开发者无需针对不同平台修改代码或重新编译,只需在不同的操作系统中安装对应的 JVM 即可。
2.JVM由哪些部分构成?每个部分起到什么作用?
- 类加载:类加载器将
.class
文件加载到方法区。 - 内存分配:对象实例在堆中分配,方法调用栈帧在JVM栈(虚拟机栈/Java方法栈)中创建。
- 执行代码:解释器或JIT执行字节码,程序计数器跟踪执行位置。
- 垃圾回收:GC自动回收堆中无用的对象。
- 本地调用:通过JNI调用操作系统功能(如网络、文件操作)。
3.什么是双亲委派?双亲委派的两大作用是什么?
双亲委派就像你家里有一个“家族规矩”:孩子遇到问题,先找爸妈解决,爸妈搞不定再找爷爷奶奶,谁有能力解决谁上,绝不自己瞎折腾。在Java里,这就是类加载器(ClassLoader)加载类的规则。
举个例子🌰:
假设你要加载一个类(比如 java.lang.String
),流程是这样的:
-
你(App类加载器):
👉 先问你的“爸爸”Ext类加载器:“你能加载这个类吗?”
👨 Ext类加载器又转头问他的“爸爸”Bootstrap类加载器:“你能加载吗?”
👴 Bootstrap类加载器(家族最牛大佬)一看:“哦,这是JDK核心类啊,我来!” → 成功加载。 -
如果是你写的类(比如
com.example.MyClass
):
👉 Bootstrap说:“这我不认识,让儿子Ext试试。”
👨 Ext类加载器也摇头:“这也不是我的活。”
👦 最后回到 你(App类加载器) :“好吧,我来加载!” → 成功加载你的类。 -
如果有人想捣乱(比如自己写一个
java.lang.String
):
👉 Bootstrap一看名字是java.lang.String
,直接加载JDK自带的,根本不会给你机会加载自己的版本 → 防止核心类被篡改。
为什么要有这种“家族规矩”?
-
避免类的重复加载:
比如你和你爸都买同一本书,纯属浪费。双亲委派确保一个类只加载一次,全家共享。 -
防止核心API被篡改:
JDK的核心类(如java.lang.*
)必须由Bootstrap类加载器加载,防止你写个恶意类替换掉它们。 -
分工明确:
- Bootstrap:加载JDK核心类(
rt.jar
等)。 - Ext:加载扩展库(
jre/lib/ext
下的jar包)。 - App:加载你写的代码(
classpath
下的类)。
- Bootstrap:加载JDK核心类(
破坏双亲委派的场景(进阶了解)
有些特殊情况会打破这个规矩,比如:
- Tomcat:每个Web应用用自己的类加载器,防止不同应用的类冲突。
- JDBC:用线程上下文类加载器加载不同厂商的驱动。
总结:双亲委派就是“先问爸妈,不行再自己干”。
4.Tomcat为什么要自定义类加载器?
Tomcat自定义类加载器主要是为了实现Web应用隔离(防止不同应用的同名类冲突)、支持热部署(动态替换类文件无需重启),同时通过优先加载应用私有类打破双亲委派(保证应用独立性),但核心类仍委派父加载器加载以确保安全,最终满足多应用共存时的灵活性与稳定性需求。
5.程序计数器的作用是什么?
程序计数器是线程私有的内存区域,用于记录当前线程正在执行的字节码指令地址,确保线程切换后能准确恢复到执行位置。
6.虚拟机栈(Java方法栈、JVM栈)的作用是什么?
虚拟机栈核心作用有三点:
第一,管理方法调用的栈帧,存储方法执行时的局部变量和中间结果;
第二,通过线程独立的栈结构隔离不同线程的执行状态;
第三,控制方法调用深度,避免无限递归导致内存溢出。
7.堆的作用是什么?
堆是JVM中最重要的一块区域,所有的对象和数组被创建后都会存放在堆中,在执行字节码指令时,会把创建的对象存入堆中,对象对应的引用地址存入虚拟机栈中的栈帧中,不过当方法执行完之后,刚刚所创建的对象并不会立马被回收,而是要等JVM后台执行GC后,对象才会被回收。
8.GC如何判断对象是否可回收?
通过可达性分析,从GC Roots出发,未被引用的对象标记为可回收。
9.垃圾回收算法有哪些?
- 标记-清除(Mark-Sweep) :标记存活对象,清除未标记对象(简单但碎片化)。
- 复制算法(Copying) :将存活对象复制到另一块内存(适用于年轻代,无碎片但空间浪费)。
- 标记-整理(Mark-Compact) :标记存活对象后整理到内存一端(适用于老年代,解决碎片问题)。
- 分代收集(Generational) :结合上述算法,只是一种理念而不是具体算法,年轻代用复制,老年代用标记-清除或标记-整理。
老年代存放长期存活的对象。
10.常见的垃圾收集器有哪些?
CMS和G1的区别?
- CMS:以最短停顿时间为目标,采用标记-清除算法,存在内存碎片问题。
- G1:将堆划分为2048个Region,分了Eden区、S0区、S1区、老年代。
CMS和G1的核心区别在于设计目标与实现机制。CMS通过并发标记清除实现低延迟,但存在内存碎片和Full GC风险,适合小堆且对延迟敏感的场景;G1将堆划分为Region,通过Mixed GC(预测模型)在可控停顿时间内平衡吞吐量,适合大堆和稳定延迟需求。从JDK9开始,G1已成为默认回收器,而CMS已逐步淘汰。