栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

大厂面试被虐记,大概率会被问到的JVM面试题

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

大厂面试被虐记,大概率会被问到的JVM面试题

威哥

今天的话题,要给大家分享的是 阿里面试被虐记,99%都会被问到的 JVM 面试题,其实关于 JVM 的面试题还是很广的,你想, JVM 都可以写成厚厚的一本书,不管从哪个角度切入面试,都会问到底层原理,只有对 JVM 理解透彻,才能在面试过程得心应手,当然啦,理解 JVM 原理,才可以让我们在互联网大型项目中的解决性能调优问题。所以,掌握JVM底层原理与调优,JAVA路上才能更上3层楼,哪3楼,看文末。

先推荐两本威哥觉得不错的JVM书籍,学到是赚到

JVM太庞大,大厂面试问哪些?
使用过以下问题的大厂有 :阿里 / 字节跳动 / 京东等
1、什么情况下会发生栈内存溢出?
2、JVM内存模型是怎样的?
3、JVM中一次完整的GC是什么样子的?对象如何晋升到老年代?
4、Java中的垃圾回收知道吗?双亲委派模型是怎么回事?
5、说说你项目中是怎么优化 JVM 的?
......


先了解一下面试管的预期,知已知彼方能百战百胜,只有了解面试官的心里预期,方能问有所答,如果你是答非所问,那不白扯吗,好,兄弟们,面试正式开始。

面试:第一回合

面试正式开始,严肃起来哈,有素质的面试官一般都会把面试气氛搞得轻松一点,这样好让面试者发挥水平,我们的面试场景是这样的:

面试官:年轻人,不要紧张,先来个简单的问题:什么情况下会发生栈内存溢出?
面试者:俺怎么也是从业三载有余,这问题也太简单了吧!是不是小看我?

1、栈是线程私有的,栈的生命周期和线程一样,每个方法在执行的时候就会创建一个栈帧,它包含局部变量表、操作数栈、动态链接、方法出口等信息,局部变量表又包括基本数据类型和对象的引用;
2、当线程请求的栈深度超过了虚拟机允许的最大深度时,会抛出StackOverFlowError异常,比如方法递归调用可能会出现该问题;
3、调整参数-xss去调整jvm栈的大小


面试:第二回合
三年工作经验是吧,这个问题应该没问题吧:
说说JVM内存模型?

你心里想,上班第0年就知道这个问题了,你说屌不屌?
兄弟们,我们可以这样说:

jvm将虚拟机分为5大区域,程序计数器、虚拟机栈、本地方法栈、java堆、方法区;

程序计数器:线程私有的,是一块很小的内存空间,作为当前线程的行号指示器,用于记录当前虚拟机正在执行的线程指令地址;

虚拟机栈:线程私有的,每个方法执行的时候都会创建一个栈帧,用于存储局部变量表、操作数、动态链接和方法返回等信息,当线程请求的栈深度超过了虚拟机允许的最大深度时,就会抛出StackOverFlowError;

本地方法栈:线程私有的,保存的是native方法的信息,当一个jvm创建的线程调用native方法后,jvm不会在虚拟机栈中为该线程创建栈帧,而是简单的动态链接并直接调用该方法;

堆:java堆是所有线程共享的一块内存,几乎所有对象的实例和数组都要在堆上分配内存,因此该区域经常发生垃圾回收的操作;
方法区:存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码数据。即永久代,
在jdk1.8中不存在方法区了,被元数据区替代了,原方法区被分成两部分;1:加载的类信息,2:运行时常量池;加载的类信息被保存在元数据区中,运行时常量池保存在堆中;
一口气把各个模块重点都说一遍,这可不能靠背,能背下来也算你本事,反正我是背不下来,你要问我是咋记住的,我是天天玩这个的呀,课也经常讲,能不熟吗,兄弟们,你们想记住的办法只有一个,那就是充分理解原理,用自己的话表达出来。

这个问题答不上来,那你只能拿块豆腐去把头撞烂了,记住了兄弟们


面试:第三回合


面试官心想,年轻人,不要狂躁,先回答一下这个问题:

JVM中一次完整的GC是什么样子的?对象如何晋升到老年代?

你心想,这不是狂躁,是自信,上班三年了,大哥!
好吧,靠实力,讲真,这些东西,是必须记在脑子里的,要不然你说搞几年开发,打死我都不信,打不死也不信。

java堆 = 新生代+老年代;
新生代 = Eden + Suivivor(S0 + S1),默认分配比例是8:1:1;
当Eden区空间满了的时候,就会触发一次Minor GC,以收集新生代的垃圾,存活下来的对象会被分配到Survivor区
大对象(需要大量连续内存空间的对象)会直接被分配到老年代
如果对象在Eden中出生,并且在经历过一次Minor GC之后仍然存活,被分配到存活区的话,年龄+1,此后每经历过一次Minor GC并且存活下来,年龄就+1,当年龄达到15的时候,会被晋升到老年代;
当老年代满了,而无法容纳更多对象的话,会触发一次full gc;full gc存储的是整个内存堆(包括年轻代和老年代);
Major GC是发生在老年代的GC,清理老年区,经常会伴随至少一次minor gc;
哦了。说完了
 

面试:第四回合

面试官:非杀杀你锐气不可:聊聊Java中的垃圾回收算法?
面试者:Java中有四种垃圾回收算法,分别是标记清除法、标记整理法、复制算法、分代收集算法;

面试官:分别说说这四种算法的特点以及在哪使用?
面试官:前辈,请听我慢慢唠叨!。。。
非杀杀你锐气不可:聊聊Java中的垃圾回收算法?

Java中有四种垃圾回收算法,分别是标记清除法、标记整理法、复制算法、分代收集算法;
标记清除法:
第一步:利用可达性去遍历内存,把存活对象和垃圾对象进行标记;
第二步:在遍历一遍,将所有标记的对象回收掉;
特点:效率不行,标记和清除的效率都不高;标记和清除后会产生大量的不连续的空间分片,可能会导致之后程序运行的时候需分配大对象而找不到连续分片而不得不触发一次GC;

标记整理法:
第一步:利用可达性去遍历内存,把存活对象和垃圾对象进行标记;
第二步:将所有的存活的对象向一段移动,边界以外的对象都回收掉;
特点:适用于存活对象多,垃圾少的情况;需要整理的过程,无空间碎片产生;

复制算法:
将内存按照容量大小分为大小相等的两块,每次只使用一块,当一块使用完了,就将还存活的对象移到另一块上,然后在把使用过的内存空间移除;
特点:不会产生空间碎片;内存使用率极低;

分代收集算法:
根据内存对象的存活周期不同,将内存划分成几块,java虚拟机一般将内存分成新生代和老生代,在新生代中,有大量对象死去和少量对象存活,所以采用复制算法,只需要付出少量存活对象的复制成本就可以完成收集;老年代中因为对象的存活率极高,没有额外的空间对他进行分配担保,所以采用标记清理或者标记整理算法进行回收;


这哥们再把算法在哪使用说下就更好了,
使用标记清除算法的垃圾回收器 比如 CMS
使用标记整理算法的比如 老年代的 serial old(串行)单 CPU client,parallel old(并行)在后台运算而不需要太多交互的任务,G1(标记整理+复制算法)
使用复制算法的比如 新生代的 serial(串行)单 CPU client,parNew(并行)多 CPU,parallel Scavenge(并行)在后台运算而不需要太多交互的任务
回答问题可以从三个角度:问题原理,优缺点,应用

面试官:如何判断一个对象是否存活?
面试者:判断一个对象是否存活,两种算法 引用计数法和可达性分析算法;

引用计数法:
给每一个对象设置一个引用计数器,当有一个地方引用该对象的时候,引用计数器就+1,引用失效时,引用计数器就-1;当引用计数器为0的时候,就说明这个对象没有被引用,也就是垃圾对象,等待回收;
缺点:无法解决循环引用的问题,当A引用B,B也引用A的时候,此时AB对象的引用都不为0,此时也就无法垃圾回收,所以一般主流虚拟机都不采用这个方法;
这是一个古老的方法,我们只要了解一下就行了

可达性分析法
从一个被称为GC Roots的对象向下搜索,如果一个对象到GC Roots没有任何引用链相连接时,说明此对象不可用,在java中可以作为GC Roots的对象有以下几种:

注意听好啰
虚拟机栈中引用的对象
方法区类静态属性引用的变量
方法区常量池引用的对象
本地方法栈JNI引用的对象
继续憋尿哈
一个对象满足上述条件的时候,就会被回收吗,答案当然是不会马上被回收,还需要进行两次标记;第一次标记:判断当前对象是否有finalize()方法并且该方法没有被执行过,若不存在则标记为垃圾对象,等待回收;若有的话,则进行第二次标记;第二次标记将当前对象放入F-Queue队列,并生成一个finalize线程去执行该方法,虚拟机不保证该方法一定会被执行,这是因为如果线程执行缓慢或进入了死锁,会导致回收系统的崩溃;如果执行了finalize方法之后仍然没有与GC Roots有直接或者间接的引用,则该对象会被回收;可达性分析其实就是利用标记-清除(mark-sweep),就是标记可达对象,清除不可达对象。
面试官,你就说小弟我答得6不6吧,嘿嘿

面试:第五回合


面试官:小伙不错哟,有哪几种垃圾回收器,有哪些优缺点?cms和g1的区别?
面试者:有点多呀,没关系,正好哥们来之前练过 ^^

垃圾回收器主要分为以下几种:Serial、ParNew、Parallel Scavenge、Serial Old、Parallel Old、CMS、G1;

(1)Serial 垃圾收集器

处理 GC 的只有一条线程,并且在垃圾回收的过程中暂停一切用户线程。

这可以说是最简单的垃圾回收器,但千万别以为它没有用武之地。因为简单,所以高效,它通常用在客户端应用上。因为客户端应用不会频繁创建很多对象,用户也不会感觉出明显的卡顿。相反,它使用的资源更少,也更轻量级。

(2)ParNew 垃圾收集器

ParNew 是 Serial 的多线程版本。由多条 GC 线程并行地进行垃圾清理。清理过程依然要停止用户线程。

ParNew 追求“低停顿时间”,与 Serial 唯一区别就是使用了多线程进行垃圾收集,在多 CPU 环境下性能比 Serial 会有一定程度的提升;但线程切换需要额外的开销,因此在单 CPU 环境中表现不如 Serial。

(3)Parallel Scavenge 垃圾收集器

另一个多线程版本的垃圾回收器。它与 ParNew 的主要区别是:
Parallel Scavenge:追求 CPU 吞吐量,能够在较短时间内完成指定任务,适合没有交互的后台计算。弱交互强计算。

ParNew:追求降低用户停顿时间,适合交互式应用。强交互弱计算。

老年代垃圾收集器
(1)Serial Old 垃圾收集器
与年轻代的 Serial 垃圾收集器对应,都是单线程版本,同样适合客户端使用。
年轻代的 Serial,使用复制算法。
老年代的 Old Serial,使用标记-整理算法。
(2)Parallel Old
Parallel Old 收集器是 Parallel Scavenge 的老年代版本,追求 CPU 吞吐量。
3)CMS 垃圾收集器
CMS(Concurrent Mark Sweep)收集器是以获取最短 GC 停顿时间为目标的收集器,它在垃圾收集时使得用户线程和 GC 线程能够并发执行,因此在垃圾收集过程中用户也不会感到明显的卡顿。  

发生STW(Stop the world),是 垃圾回收为了保证程序不会乱套,不然在垃圾回收的时候(不管是标记还是整理复制),又有新的对象进入怎么办?
最好的办法就是暂停用户的一切线程。也就是在这段时间,你是不能 new 对象的,只能等待。
表现在 JVM 上就是短暂的卡顿,什么都干不了。这个头疼的现象,就叫作 Stop the world。简称 STW。

G1垃圾回收器是在Java7 update 4之后引入的一个新的垃圾回收器。G1是一个分代的,增量的,并行与并发的标记-复制垃圾回收器。
它的设计目标是为了适应现在不断扩大的内存和不断增加的处理器数量,进一步降低暂停时间(pause time),同时兼顾良好的吞吐量。

G1回收器和CMS比起来,有以下不同:
G1垃圾回收器回收得到的空间是连续的。这避免了CMS回收器因为不连续空间所造成的问题。
G1回收器的内存与CMS回收器要求的内存模型是不同的。G1将内存划分一个个固定大小的区域,每个区域可以是年轻代、老年代的一个。内存的回收是以区域作为基本单位的;
G1还有一个及其重要的特性:软实时(soft real-time)。
“软实时”则是指,用户可以指定垃圾回收时间的限时,但是G1并不担保每次都能在这个时限内完成垃圾回收,
通过设置合理的目标,可以让达到90%以上的垃圾回收时间都在这个时限内。

面试:第六回合

CMS有什么问题?
1、先说并发
并发意味着多线程抢占CPU资源,即GC线程与用户线程抢占CPU。这可能会造成用户线程执行效率下降。
CMS默认的回收线程数是(CPU个数+3)/4。这个公式的意思是当CPU大于4个时,保证回收线程占用至少25%的CPU资源,这样用户线程占用75%的CPU,这是可以接受的。
但是,如果CPU资源很少,比如只有两个的时候怎么办?按照上面的公式,CMS会启动1个GC线程。相当于GC线程占用了50%的CPU资源,这就可能导致用户程序的执行速度忽然降低了50%,50%已经是很明显的降低了。
这种场景怎么处理呢?
我给的答案是可以不用考虑这种场景。现在的PC机中都至少有双核处理器,更别说大型的服务器了。

2、并发清理阶段用户线程还在运行,这段时间就可能产生新的垃圾,新的垃圾在此次GC无法清除,只能等到下次清理。这些垃圾有个专业名词:浮动垃圾。

由于垃圾回收阶段用户线程仍在执行,必需预留出内存空间给用户线程使用。因此不能像其他回收器那样,等到老年代满了再进行GC。

总结一下:
CMS采用了多种方式尽可能 降低GC的暂停时间,减少用户程序停顿。
停顿时间降低的同时牺牲了CPU吞吐量 。
这是在停顿时间和性能间做出的取舍,可以简单理解为"空间(性能)"换时间。

面试:第七回合


面试官继续追问:
1、什么是双亲委派模型?
2、为什么需要双亲委派模型?
3、怎么打破双亲委派模型?

wokao,这是打算往死里问吗?哥哥,我是来搬砖的,工资减1000块钱行不行,这是打破沙锅问到底啊的节奏啊。
其实,这个问题也是JVM里必须要了解的问题,如果你还不了解,只能说明你对JVM的理解还不到位,别慌,咱们可以这样回答:
当一个类加载器收到一个类加载的请求,他首先不会尝试自己去加载,而是将这个请求委派给父类加载器去加载,只有父类加载器在自己的搜索范围类查找不到给类时,子加载器才会尝试自己去加载该类;这是是双亲委派模型。
为了防止内存中出现多个相同的字节码;因为如果没有双亲委派的话,用户就可以自己定义一个java.lang.String类,那么就无法保证类的唯一性;
打破双亲委派模型,可以用自定义类加载器,继承ClassLoader类,重写loadClass方法和findClass方法;
......
面试依然在继续......
-----------------------------------------------------------------------
很多程序员吐槽说:
面试造火箭,工作拧螺丝

其实,这是程序员自我调侃的方式而已,真实的情况是
代码十多载,大有人才在,
项目越复杂,架构越重要;
原理若不会,面试就吃亏,
源码要吃透,知其所以然;
算法是基础,不会笑大牙,
都想拿高薪,这是硬道理。

不管你是在哪个阶段,如果你跟威哥一样热爱技术,那就让我们结伴同行,做一个积极向上,充满正能量的、纯洁的程序员,总有一天,你会站在更高的职场舞台,叱诧互联网大厂。

文章内容有限,如果你觉得还不过瘾,并且正在准备学习JVM,或者正在备孕即将到来的跳槽期,威哥推荐你把文章开头两本书买了,威哥再推荐给你一套索尔老师刚录制的《JVM源码分析与调优实战》视频,这套视频不仅内容深入含金量高(小白请左转,勿闯),并且带有实战场景调优讲解,白嫖福利,啥也不说了,拿走不谢,学成之后,请威哥喝杯奶茶就行。这套视频内容直接让你直达3楼,哪3楼?
1楼工程师,2楼高级工程师,3楼架构师。

 

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/704853.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号