大家可能在面试中,或多或少都遇到JVM相关的面试题,但是有个问题,就是JVM其实知识点挺多的,本文以笔者亲身经历为主,通过一个JVM的常见面试题,进行逐步了解,学习JVM。
这一切都是逼的,回想我刚工作的时候,然后被问JVM,那会儿第一反应,JVM是啥,现在在看看各个招聘岗位,或多或少都要求会JVM。然后面试者,也开始将JVM作为必备技能,虽然可能百分之95的程序员在实际工作中,不会用到任何和JVM相关的知识点。
正常的面试官问题,其实大多都是,询问你是否了解某种技术,或者看你简历写了某种技术,然后问的。很少有那种直接上来就问特别细的问题。当然笔者就遇到一个很无语的面试题。
面试官:你给我说说Spring Boot中的metadata.properties是干嘛用的?
面试者(我):内心OS(这问题问的真尼玛没有水平,谁会记得这么细节的东西),好在我他娘的真的记住了我打算从Spring Boot的启动类@SpringBootApplication开始说起,刚开始说启动原理,面试官就打断我,说他问什么,我答什么。最后我回答metadata.properties封装到Metadata对象中,用于启动加载配置(后面的面试全程一问一答,完全没意思),当然这种存粹是特例。
比如大多还是采用下面的问法,反正我是没见过一上来就问你什么是G1、CMS等具体很细节的技术的。
面试官:了解JVM吗?/项目中有用到JVM吗?我看你简历上写精通(熟练掌握)JVM,那你给我说说…。
面试者:对于JVM而言,我们首先弄清楚一个概念,就是对象的可达或者不可达的概念,通过根搜索算法,判断对象是可达还是不可达,如果是不可达的,则表示可以被垃圾回收机制回收。在垃圾回收中有这么一些常见的垃圾回收策略:标记、复制、压缩、分代。其中从效果来说的话,最好的是分代策略。在我们对JVM进行优化的时候,除了对服务器资源进行分配以外,垃圾收集器一般现在我们都是采用并行收集器(Parallel)或者是垃圾优先收集器(G1)
以上回答是我个人进行整理常见的一个万能回答模板。这个模板差不多把JVM一些主要的知识点串联起来方便记忆,此外也能向面试官表达,我对JVM知识点掌握的比较全面。当然,但凡这个面试官有点水平,或者认真听你回答了,大概率是要对你回答的内容里的知识点重新提问。比如:
面试官:刚才你说,通过根搜说算法,判断可达不可达,那你说说根搜索算法实现呗?
面试官:你刚才说的这几种垃圾回收策略,你能说说他们的应用场景或者区别吗?
面试官:你刚才说效果最好的垃圾回收效果是分代策略,你能详细说以下吗?
面试官:刚才你说到了,垃圾收集器,你能说说你知道的收集器吗?
面试官:你能说说为什么我们现在都是采用并行收集器或者垃圾优先收集器吗?
笔者认为,一个好的面试体验,应该是面试官抛出一个话题,然后你在这个话题上面做一个多方面的概括,这样做是为了让面试官知道我们在这个知识领域的范围宽度。然后面试官根据我们回答的多个方面,从中选择一个面试官感兴趣的点,继续往下深问。这样既能考察你的广度,也能考察你的深度。
所以笔者建议,快速学习一个知识点,并且灵活运用,还是自己通过一个概念总结蜘蛛网,形成一个思维导图,串联多个知识点,形成一个万能模板。当面试官问你问题的时候,只要这个问题在你蜘蛛网内,你都可以使用这个模板进行回答。就算那个领域你不怎么会,但是你也可以通过的蜘蛛网,思维导图,向面试官表达,其他你会的方面。
比如,面试官上来就问题你,说说设计模式中的观察者模式呗,不会咋办,你要说不会,不就尴尬了,但是你确实不会,这时候只能硬着头皮给面试官说观察者模式了解不多,对工厂模式了解比较多,如果面试官没有让你停止回答,接下来你就可以说工厂模式了,然后通过工厂模式,将话题转移到Spring的依赖反转,面试官如果没让你继续停止,接下来你不就能说Spring了吗?
根搜索算法的基本思路就是通过一系列名为”GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。可以被垃圾回收机制回收。
不定时去堆内存中清理不可达对象。但不可达的对象并不会马上就会直接回收。
新生代 GC(Minor GC):回收非常频繁,一般回收速度也比较快。Minor GC触发机制:当年轻代的Eden区满时就会触发Minor GC,
老年代 GC(Major GC / Full GC):出现了 Major GC,经常会伴随至少一次的 Minor GC。Full GC将会同时回收新生代、老年代。
垃圾回收不会发生在永久代,如果永久代满了或者是超过了临界值,会触发完全垃圾回收(Full GC)。(注:Java8中已经移除了永久代,新加了一个叫做元数据区的native内存区)
-XX:+PrintGC 每次触发GC的时候打印相关日志
-XX:+Use G1GC 垃圾优先收集器
-XX:+PrintGCDetails 更详细的GC日志
-Xms 堆初始值
-Xmx 堆最大可用值
-Xmn 新生代堆最大可用值
-Xss 线程最大调用深度
-XX:SurvivorRatio 用来设置新生代中eden空间和from/to空间的比例.
-XX:NewRatio 配置新生代与老年代占比 1:2
含以-XX:SurvivorRatio=eden/from=den/to
总结:在实际工作中,我们可以直接将初始的堆大小与最大堆大小相等,
这样的好处是可以减少程序运行时垃圾回收次数,从而提高效率。
解决方法:调整-Xmx
产生于递归调用。循环调用不会产生栈溢出,但循环方法中产生递归也会发生栈溢出。
解决方法:调整-Xss
内存溢出:要求分配的内存超出了系统能给的,系统不能满足需求,于是产生溢出。
内存泄漏:内存泄漏伴随内存溢出。内存空间没有被及时释放,长时间占用内存,最终导致内存空间不足,而出现内存溢出。
频繁GC:系统分配给每个应用的内存资源都是有限的,内存泄漏导致其他组件可用的内存变少后,一方面会使得GC的频率加剧,再发生GC的时候,所有进程都必须等待,GC的频率越高,用户越容易感应到卡顿。另一方面内存变少,可能使得系统额外分配给该对象一些内存,而影响整个系统的运行情况。
导致程序运行崩溃:一旦内存不足以为某些对象分配所需要的空间,将会导致程序崩溃,造成体验差。
1加载;2连接;初始化
加载:查找并加载类的二进制数据(将硬盘中的字节码文件加载到内存中)
连接:验证、准备、解析
验证:验证所加载的类的正确性(验证类文件结构、语义、字节码、二进制兼容性)
准备:为类的静态变量分配内存,并初始化为默认值
解析:把类中的符号引用(方法调用)转化为直接引用(指针)
初始化:为类的静态变量赋值。
若没有加载,连接,则先加载,连接。
若存在父类,且父类未初始化,则先初始化父类
若父类存在初始化语句,依次执行初始化语句
强引用(Strong Ref):通常我们编写的代码都是强引用,因此相对应的是强可达性,只有去掉强可达性,对象才能被回收。
超出引用的作用域或者显示的将引用赋值为null,就可以被垃圾收集。
软引用(Soft Ref):对应软可达性,只要有足够的内存就一直保持对象,直到发现内存不足且没有强引用的时候才回收对象。
弱引用(Weak Ref):比软引用更弱,当发现不存在强引用的时候会立即回收此类型的对象,而不需要等到内存不足。通过java.lang.ref.WeakReference和java.util.WeakHashMap类实现。
虚引用(Phantom Ref):根本不会在内存中保持该类型的对象,只能使用虚引用本身,一般用于在进入finalize()方法后进行特殊的清理过程,通过java.lang.ref.PhantomReference实现。