【2026版】JVM面试题
文章目录
- 1. JVM的运行时内存区域是怎样的?
- 2. Java中的对象一定在堆上分配内存吗?
- 3. Java的堆是如何分代的?
- 4. 对象的分代晋升机制?
- 5. YoungGC和FullGC的触发条件是什么?
- 6. 什么是Stop The World?
- 7. JVM有哪些垃圾回收算法?
- 8. JVM如何判断对象是否存活?
- 9. 哪些内容可以作为GCroots?
- 10. 什么是三色标记算法?
- 11. 新生代和老年代的GC算法
- 12. 常见的垃圾回收器
- 13. G1和CMS有什么区别?
- 14. 什么是双亲委派?
内容参考:https://www.yuque.com/hollis666/vn7dy3
1. JVM的运行时内存区域是怎样的?
2. Java中的对象一定在堆上分配内存吗?
不一定,在HotSpot虚拟机中,存在JIT优化的机制,JIT优化中可能会进行逃逸分析,当经过逃逸分析发现某一个局部对象没有逃逸到线程和方法外的话,那么这个对象就可能不会在堆上分配内存,而是进行栈上分配。
3. Java的堆是如何分代的?
4. 对象的分代晋升机制?
晋升到Survivor区
一般情况下,对象将在新生代进行分配,首先会尝试在Eden区分配对象,当Eden内存耗尽,无法满足新的对象分配请求时,将触发新生代的GC(YoungGC、MinorGC),在新生代的GC过程中,没有被回收的对象会从Eden区被搬运到Survivor区,这个过程通常被称为"晋升"。
晋升到老年代
同样的,对象也可能会晋升到老年代,触发条件主要看对象的大小和年龄。对象进入老年代的条件有三个,满足
个就会进入到老年代:
- 躲过15次GC。每次垃圾回收后,存活的对象的年龄就会加1,累计加到15次(jdk8默认的),也就是某个对象躲过了15次垃圾回收,那么JVM就认为这个是经常被使用的对象,就没必要再待在年轻代中了。具体的次数可以通过-XX:MaxTenuringThreshold来设置在躲过多少次垃圾收集后进去老年代。
- 动态对象年龄判断。规则:如果在Survivor空间中小于等于某个年龄的所有对象大小的总和大于
Survivor空间的一半时,那么就把大于等于这个年龄的对象都晋升到老年代。 - 大对象直接进入老年代。-XX:PretenureSizeThreshold来设置大对象的临界值,大于该值的就被认为是大对象,就会直接进入老年代。(PretenureSizeThreshold默认是O,也就是说,默认情况下对象不会提前进入老年代,而是直接在新生代分配。然后就GC次数和基于动态年龄判断来进入老年代。)
5. YoungGC和FullGC的触发条件是什么?
YoungGC
YoungGC的触发条件比较简单,那就是当年轻代中的eden区分配满的时候就会触发。
FullGC
-
创建一个大对象,超过指定阈值会直接保存在老年代当中,如果老年代空间也不足,会触发FullGC。
-
YoungGC之后,发现要移到老年代的对象,老年代存不下的时候,会触发一次FullGC
-
当准备要触发一次YoungGC时,会进行空间分配担保,在担保过程中,发现虚拟机会检查老年代最大可用的连续空间小于新生代所有对象的总空间,但是HandlePromotionFailure=false,那么就会触发一次FullGC(HandlePromotionFailure这个配置,在JDK7中并不在支持了,这一步骤在该版本已取消)。当准备要触发一次YoungGC时,会进行空间分配担保,在担保过程中,发现虚拟机会检查老年代最大可用的连续空间小于新生代所有对象的总空间,但是HandlePromotionFailure=true,继续检查发现老年代最大可用连续空间小于历次晋升到老年代的对象的平均大小时,会触发一次FuIIGC
-
代码中执行System.gc()的时候,会触发FullGC,但是并不保证一定会立即触发。
6. 什么是Stop The World?
Java中Stop-The-World机制简称STW,是在执行垃圾收集算法时,Java应用程序的其他所有线程都被挂起。这是Java中一种全局暂停现象,全局停顿,所有Java代码停止,native代码可以执行,但不能与JVM交互。
7. JVM有哪些垃圾回收算法?
标记清楚、标记整理、标记复制
8. JVM如何判断对象是否存活?
引用计数法
:给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加1;当引用失效,计数器就减1;任何时候计数器为0的对象就是不可能再被使用的。这个方法实现简单,效率高,但是目前主流的虚拟机中并没有选择这个算法来管理内存,其最主要的原因是它很难解决对象之间相互循环引用的问题。
可达性分析算法
:这个算法的基本思想就是通过一系列的称为“GCRoots”的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到GCRoots没有任何引I用链相连的话,则证明此对象是不可用的。
9. 哪些内容可以作为GCroots?
GC Roots是判断对象存活与否的起点,所有能被GC Roots直接或间接引用的对象都不会被回收。以下是常见的GC Roots类型:
- 虚拟机栈(栈帧中的本地变量表)引用的对象
public void foo() {Object obj = new Object(); // obj是GC Root
}
- 方法区中静态属性(类静态变量)引用的对象
class MyClass {static Object staticObj = new Object(); // staticObj是GC Root
}
- 方法区中常量(final常量)引用的对象
class MyClass {final static String CONST = "Hello"; // CONST是GC Root
}
-
本地方法栈中JNI(Native方法)引用的对象
通过JNI调用的本地代码(C/C++)中引用的Java对象。 -
Java虚拟机内部的引用
系统类加载器(ClassLoader)、Class对象、异常类(如OutOfMemoryError)等。
10. 什么是三色标记算法?
三色标记算法是一种JVM中垃圾标记的算法,他可以减少JVM在GC过程中的STW时长,他是CMS、G1等垃圾收集器中主要使用的标记算法。
三色标记法将对象分为三种状态:白色、灰色和黑色。
白色:该对象没有被标记过。
灰色:该对象已经被标记过了,但该对象的引用对象还没标记完。
黑色:该对象已经被标记过了,并且他的全部引用对象也都标记完了。
三色标记法的标记过程可以分为三个阶段:初始标记(InitialMarking)、r并发标记(ConcurrentMarking)和重新标记(Remark)。
初始标记
:遍历所有的根对象,将根对象和直接引用的对象标记为灰色。在这个阶段中,垃圾回收器只会扫描被直接或者间接引l用的对象,而不会扫描整个堆。因此,初始标记阶段的时间比较短。(StopTheWorld)并发标记
:在这个过程中,垃圾回收器会从灰色对象开始遍历整个对象图,将被引用的对象标记为灰色,并将已经遍历过的对象标记为黑色。并发标记过程中,应用程序线程可能会修改对象图,因此垃圾回收器需要使用写屏障(WriteBarrier)技术来保证并发标记的正确性。(不需要STW)重新标记
:重新标记的主要作用是标记在并发标记阶段中被修改的对象以及未被遍历到的对象。这个过程中,垃圾回收器会从灰色对象重新开始遍历对象图,将被引用的对象标记为灰色,并将已经遍历过的对象标记为黑色。(Stop The World)
多标问题
所谓多标,其实就是这个对象原本应该被回收掉的白色对象,但是被错误的标记成了黑色的存活对象。从而导致这个对象没有被GC回收掉。
这个一般发生在并发标记过程中,该对象还是有引用的,但是在过程中,应用程序执行过程中把他的引|用关系删除了,导致他变成了一个垃圾对象。
多标的话,会产生浮动垃圾,这个问题一般都不太需要解决,因为这种垃圾一般都不会太多,另外在下一次GC的时候也都能被回收掉。
漏标问题
具体的解决方式,在CMS和G1中也不太一样。CMS采用的是增量更新方案,G1则采用的是原始快照的方案。
11. 新生代和老年代的GC算法
新生代一般采用标记复制算法,老年代一般采用标记整理算法,不过CMS是用的标记清除算法
12. 常见的垃圾回收器
除了CMS,G1, ZGC,其他的垃圾回收器都会全程STW
13. G1和CMS有什么区别?
14. 什么是双亲委派?
双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,先检查是否已经加载过这个类,如果有则直接返回,然后它也不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。