首页 > 技术交流 > JVM相关面试题汇总

JVM相关面试题汇总

头像
心君D悦子
编辑于 2021-04-21 01:30:24 APP内打开
赞 8 | 收藏 59 | 回复1 | 浏览1235
作为一名Java程序员,在高级开发中,经常会进行JVM调优,这也是现在面试时的高频考点,下面我总结了我在笔面试期间所遇到的JVM相关面试题,希望对大家有所帮助,也愿各位童鞋早日上岸~!
如有 错误请指出 更正,相互学习,谢谢大家~!

注:本篇大部分为理论知识,实战调优相关面试题并未涉及,面对的是校招人群,因此也尚无调优经验。

垃圾回收篇

1、GC的两种判定方法?
①引用计数算法
②可达性分析算法

2、什么是分代回收?
不同的对象生命周期不一样,所以可以采取不同的回收方式以提高回收效率。可以分别为新生代、老年代进行垃圾回收

3、GC原理是什么,JVM怎么回收内存?
从标记阶段到清除阶段。标记阶段进行相关存活对象的标记,紧接着在清除阶段将未被标记的对象进行回收清除

4、垃圾回收算法各自的优缺点是什么?
引用计数算法实现简单,效率高;不能解决循环引用的问题,导致内存泄漏
标记清除算法实现简单,能够解决循环引用的问题;会产生内存碎片,因此需要维护一个空闲列表
标记整理算法不会产生内存碎片,不会消耗2倍内存;效率较低
复制算法效率很高,不会产生内存碎片;需要消耗两倍内存,占用较大

5、说下G1的应用场景,如何搭配使用垃圾回收器的?
面向服务端应用,针对具有大内存、多处理器的机器;最主要的应用是需要低GC延迟,并具有大堆的应用程序提供解决方案

6、垃圾回收算法的实现原理?
引用计数算法:对于一个对象A,只要有任何一个对象引用了A,则A的引用计数器就+1;当引用失效时,引用计数器就-1。只要对象A的引用计数器的值为0,即表示对象A不可能再被使用,可进行回收
可达性分析算法:由根GC Roots开始向下遍历整个对象树,如果一个对象没有任何引用链,则将会被回收
标记清除算法:从引用根节点开始遍历,标记所有被引用的对象,一般是在对象的Header中记录为可达对象;再对堆内存从头到尾进行线性的遍历,如果发现某个对象在其Header中没有标记为可达对象,则予以回收
复制算法:将内存空间分为两块,每次只使用其中一块,在垃圾回收时将正在使用的内存中的存活对象复制到未被使用的内存块中,之后清除正在使用的内存块中的所有对象,交换两个内存的角色,最后完成垃圾回收
标记压缩算法:第一阶段和标记-清除算法一样,从根节点开始标记所有被引用的对象;第二阶段将所有的存活对象压缩到内存的一端,按顺序排放;之后,清理边界外所有的空间

7、system.gc()和runtime.gc()会做什么事情?
二者功能一样,都会显式触发Full GC,同时对新生代和老年代进行回收

8、GC Roots有哪些?
虚拟机栈的栈帧的局部变量表中的引用
本地方法栈的JNI引用对象
方法区中类的静态属性
方法区中的常量
被同步锁synchronized持有的对象
对于分区的G1,可能会存在将另一个区域的对象也临时作为GC Roots

9、CMS解决了什么问题?说一下回收的过程?
CMS主打低延迟,以快速响应用户为目标,可以和用户线程同时执行,提高用户体验
①初始标记:仅标记出GC Roots能直接关联到的对象
②并发标记:从GC Roots开始遍历整个对象引用链
③重新标记:修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录
④并发清除:清理删除标记阶段判断的已经死亡的对象

10、CMS回收停顿了几次?为什么要停顿?
停顿了两次,因为CMS虽然是主打低延迟的,但初始标记阶段和重新标记阶段不得不暂停用户线程,初始标记阶段的停顿以方便后续GC线程与用户线程并发的执行后续的间接标记存活对象,重新标记阶段的停顿是为了彻底标记之前被遗漏的部分

11、GC是什么?为什么要有GC?什么情况下触发垃圾回收?
GC是垃圾回收
因为程序不断运行,不断有新的对象产生,为了避免无用对象的存在从而占用内存空间导致内存不足,因此要进行回收
当新生代满时触发Young GC,当老年代满时触发Full GC/Major GC

12、你知道哪几种垃圾回收器,各自的优缺点,重点讲一下CMS和G1和各自的特点
Serial/Serial Old,串行回收,简单高效,只适合单CPU
ParNew/CMS,新生代并行回收,效率提升,老年代并发回收,STW时间急剧降低。ParNew仍然会STW,CMS会产生内存碎片,无法处理并发清除阶段产生的垃圾,吞吐量不高
Parallel/Parallel Old,并行回收,吞吐量优先,可以控制吞吐量以及自适应调节策略。但暂停时间会变高,响应慢
CMS是并发标记清除的GC,使用的是标记清除算法,能够与用户线程并发执行,无需STW,能达到低延迟的目标
G1,基于分区算法,同样是分代收集,无内存碎片,可预测的停顿时间模型,同时实现并行与并发。但在堆内存过小时表现不良好,只有在大堆环境下优势俞明显,且内存占用较高

13、说一下 jvm 有哪些垃圾回收器?
传统的有7大垃圾收集器:串行回收器Serial、SerialOld、并行回收器ParNew、Parallel、Parallel Old、并发回收器CMS、G1
后来新增的有:OpenJDK12的Shenandoah收集器,主打低延迟;以及jdk11发布了ZGC,可伸缩的低延迟垃圾收集器,当然这时还只是实验性版本。

14、JVM GC算法有哪些,目前的JDK版本采用什么回收算法
标记阶段:引用计数算法,可达性分析算法
清除阶段:标记清除,标记压缩,复制算法,分代收集算法,增量收集算法,分区算法
新生代采用复制算法,老年代采用标记清除和标记压缩算法

15、讲下G1的回收过程
①新生代回收:当Eden满时触发Young GC,是一个并行的收集器,此时需要STW,随后进行垃圾回收
②老年代并发标记:当堆内存使用达到一定值(默认45%),进行并发标记
③混合回收:当越来越多的对象晋升到老年代region时,为了避免堆内存被耗尽,虚拟机会触发一个混合的垃圾收集器,即Mixed GC。同时回收新生代与老年代
④Full  GC(如果以上不能正常工作,则执行此阶段):G1会STW,使用单线程的内存回收算法进行垃圾回收,性能会很差,应用程序停顿时间也会很长

16、详细介绍一下 CMS 垃圾回收器?
CMS收集器是并发收集器,能够实现用户线程与GC线程同时运行,同时它是老年代收集器,算法采用的是标记清除算法,并且仍然会stw,即使它是并发垃圾收集器,但并不能实现真正意义上的并发效果,在回收时仍然会短暂stw,但停顿时间比其他算法小得多。
回收过程主要分4步:
①初始标记:仅仅标记处GC Roots能直接关联到的对象,这个时候是STW的,因为只有一小部分的GC Roots会被标记,因此停顿是非常短暂的。
②并发标记:从GC Roots开始遍历整个对象引用链,这个过程是并发的,与用户线程同时进行,不会STW
③重新标记:用于修正在并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象进行重新标记。此过程是stw的,但因为这些对象占少数,所以不会停顿过长时间
④并发清除:清理标记阶段未被标记的对象,释放内存空间,此过程是并发的,可以和用户线程一起运作。
因为最耗费时间的并发清除和并发标记阶段是并发的,不需要暂停工作,所以整体的回收还是低延迟的。

17、怎么判断对象是否可以被回收?
有两种算法:引用计数器、可达性分析算法
引用计数器:一个对象被引用1次则计数加1,当引用失效时计数-1,那些计数为0的对象就代表没有任何引用,可以执行回收
可达性分析算法:根据GC Roots引用链自上而下进行判断各个目标对象是否可达,如果可达则进行标记,算法执行完毕后,那些没有标记的则视为无引用对象,可以执行回收

18、新生代垃圾回收器和老生代垃圾回收器都有哪些?有什么区别?
新生代收集器:串行Serial、并行ParNew、并行Parallel
老年代收集器:串行Serial Old、并发CMS、并行Parallel Old
新生代收集器用于回收新生代,老年代收集器用于回收老年代

19、简述分代垃圾回收器是怎么工作的?
一般分为两个分代,新生代和老年代,默认占比为2:1。新生代采用的是复制算法,而老年代采用的是标记整理或标记清除算法。新生代有3个分区,一个Eden区,两个Survivor区,新创建的对象会先放到Eden区中,当Eden空间满时,触发YoungGC,进行垃圾回收,存活的对象放进Survivor To区中,并且年龄计数+1,每当执行YoungGC时候,都会将Survivor From区移动到SurvivorTo区,年龄+1,直到年龄为15后,将会转至老年代

20、说一下 jvm 调优的工具?
jdk自带的有jconcole,jvisualvm
eclipse提供的MAT
GChisto、gcviewer

21、常用的 jvm 调优的参数都有哪些?
-Xms:初始堆空间内存(默认为物理内存的1/64)
-Xmx:最大堆空间内存(默认为物理内存的1/4)
-XX:NewRatio:设置老年代与新生代在堆结构的占比
-XX:SurvivorRatio:设置新生代中Eden和S0/S1空间的比例
-XX:+PrintGCDetails:输出详细的GC处理日志
-XX:PermSize:设置初始分配的永久代大小
-XX:MaxPermSize:设置永久代最大可分配空间
-XX:MetaspaceSize:设置元空间初始分配空间
-XX:MaxMetaspaceSize:设置元空间最大可分配空间

内存结构篇

1、说一下 jvm 的主要组成部分?及其作用?
①类加载子系统:负责加载class文件,只负责加载,不负责能否运行,能否运行是执行引擎决定的
②运行时数据区:是java内存中区域最大的一块
包括虚拟机栈、本地方法栈、堆内存、方法区、程序计数器,其中堆内存和方法区是线程共享的,而其他三个是线程私有的
1)堆内存:主要存放的是new出来的对象或数组,而且堆内存是多线程共享的。堆的大小可以用-Xms -Xmx来设置,其中主要分为新生代和老年代,比例默认是2:1,新生代中又分为Eden区和Survivor0、1区,比例是8:1:1
2)方法区:主要用于存放已被虚拟机加载的类型信息、方法信息、常量等资源。方法区中存在一个运行时常量池,用于存放编译期间生成的符号引用或类加载后的直接引用。jdk1.7及以后将字符串常量池从原来的方法区移动到了堆内存中,并且jdk1.8及以后不再使用永久代来实现方法区,而是使用元空间(本地内存)来实现方法区。
3)虚拟机栈:也就是我们通常说的栈内存,每个线程都含有自己的虚拟机栈。它以栈帧为单位存放数据,并且每执行一个方***生成一个栈帧,其中包含局部变量表(用于临时保存变量)、操作数栈(用于保存临时的中间变量信息)、方法返回地址(用于指定方法执行完毕后返回的地址)、动态链接(指向运行时常量池的方法引用,可以将符号引用转换为直接引用)
4)本地方法栈:为虚拟机提供Native方法服务
5)程序计数器:PC寄存器用于存放下一条指令的地址,也就是即将要执行的代码,由执行引擎来读取这里面的指令。
③执行引擎:将字节码指令解释/编译(此编译并非是字节码文件的编译)为对应平台上的本地机器指令,然后交给操作系统去执行
④本地方法接口:提供本地方法

2、说一下堆和栈的区别?
他们是运行时数据区的两个部分
①堆是线程共享的,而栈是线程私有的;
②堆一般是保存实例对象的,也就是实体,包括new出来的实例或数组,而栈是保存这些实例的引用的
③栈内存的大小远远小于堆内存大小,因为栈内存只保存引用,而不保存实例数据,而且栈内存更新速度远大于堆内存

3、队列和栈是什么?有什么区别?
队列和栈都是一种数据结构。队列是FIFO先进先出,而栈是LIFO后进先出的

4、什么是双亲委派模型(机制)?
是一种任务委派模式,就是说当一个类需要被加载时,当前的类加载器并不会去加载,而是委托给父加载器,逐层向上委托,直到顶层的引导类加载器,如果顶层加载器能够完成类的加载,则会直接执行加载,并不会让子加载器完成,这种工作模式就是双亲委派机制
比如说自己定义一个java.lang包,并创建一个String类,当程序进行String类加载时,原先是由系统类加载器进行加载的,但由于双亲委派模式的存在,所以会委托给顶层的引导类加载器进行核心类库中String类的加载,从而导致自己定义的String类无法被加载。好处就是提高代码的安全性,防止API被恶意篡改

5、说一下类加载的执行过程?
主要分为3个阶段:加载、链接、初始化
加载:通过类的全限定名获取定义此类的二进制字节流,并放到方法区中,然后在内存中生成一个代表这个类的Class对象,作为方法区中这个类的访问入口
链接:链接阶段分为3个部分:验证、准备、解析
验证:确保上面的class文件二进制字节流中包含的信息符合当前虚拟机要求,保证类加载的正确性
准备:为类变量分配内存,并且设置该类变量的默认初始值(如int型默认设为0,boolean型默认设为false)
解析:将方法区的运行时常量池中的符号引用转换为直接引用。所谓的符号引用就是使用一组符号来表示引用的目标,因为在编译阶段,编译成的.class文件并不知道实际引用类的内存地址,所以只能用符号引用来代替。比如说People类中引用了Tool类,在编译时,People类并不知道Tool类的实际内存地址,因此只能使用一组符号表示Tool类的地址。而直接引用是直接指向目标的指针。
初始化:执行类构造器方法<clinit>(),此方***自动收集所有类变量的赋值动作(内存已经在准备阶段分配完毕)和静态代码块中的语句合并而来并执行

6、java 中都有哪些引用类型?
从级别高到低说是:强引用、软引用、弱引用、虚引用
①强引用:我们日常创建的对象(new方式)就是强引用类型的,只要强引用的对象是可触及的(即可达),则垃圾收集器永远不会执行回收
②软引用:内存充足时候不会回收,只有在内存溢出前才进行回收,可以用来实现缓存,将临时的数据存储下来,也不占据过多的内存空间,即使没有内存了,垃圾收集器也能将其进行二次回收。java提供了SoftReference来实现软引用,在回收软引用时,可以将引用放到这个ReferenceQueue引用队列中,可以获取到对象的信息,进行额外的处理。
③弱引用:只要发生了GC,那么软引用直接被回收。也能实现缓存功能,因为一旦GC就会回收,不会占用太大内存。java提供了WeakReference来实现弱引用,与软引用一样,在回收时可以将弱引用放进一个引用队列,可以获取到对象的信息,跟踪回收情况
④虚引用:用于对象回收跟踪,是引用类型中最弱的一个。虚引用不能像强软弱一样单独使用,要配合引用队列进行使用,也无法通过虚引用来获取被引用的对象,当使用get()方法时,总会返回null。虚引用的作用就是能在这个对象被垃圾回收器回收时收到一个系统通知,并且能跟踪对象的回收时间。java提供了PhantomReference来实现虚引用,不过在创建引用时,需要指定引用队列ReferenceQueue

7、实例对象是怎样存储的?
对象的实例存储在堆空间;对象的类元信息存储在方法区(元空间),被对象头中的类型指针所指向;对象的引用存储在虚拟机栈

8、一个对象由哪些部分组成(堆内存中)?
对象头、实例数据、对齐填充

9、对象头包含哪些信息?
运行时元数据markword、类型指针、如果是数组对象还会包含数组长度

10、markword包含哪些信息?
锁的状态标志(如偏向锁、无锁、轻量级锁等)、分代年龄、锁记录record、如果是重量级锁的话还保存monitor对象、线程id、hashCode等




1条回帖

回帖
加载中...
话题 回帖

相关热帖

技术交流近期热帖

近期精华帖

热门推荐