应届生互联网求职面试总结分享

一篇文章彻底搞定所有GC面试问题

2019-04-17  本文已影响0人  大菜鸟_

众所周知,在C++,内存的管理是程序员的任务,包括对象的创建和回收(内存的申请和释放),而在java中,我们可以通过以下四种方式创建对象(面试考点):

而在java中对象的回收主要是GC完成:GC会在合适的时间被触发,完成垃圾回收,将不需要的内存空间回收释放,避免无限制的内存增长导致的OOM。由此可以看出,GC在java相关的应用程序中重要性,这也是为什么面试官热衷GC相关的面试问题。大部分面试,GC相关问题都是这样开始的:“你知道GC吗”?、“你了解GC机制吗”?

上面的类似提问该从何处着手呢?往下看之前,建议读者先思考:你是如何组织这个问题的回答的?这类似很“宽泛”的问题,其实并不容易回答好,会给人一种:我明明知道相关知识点,但是却又好像无话可说。比如说GC,它就是用来垃圾回收的啊,但是这样一句话不能让面试官充分了解你,你也成功的把话“聊死”了,反正不会是面试的加分项......这类宽泛的问题不仅仅考察你对知识点的掌握,其实也考察读者的文字组织、交流沟通能力~

如果博主遇到类似“宽泛”的问题,我会先预设:提出这个问题的面试官对问题的相关知识点“一无所知”。在这个前提下,我会依次从以下五个方面组织该问题的回答(这也是本文后续的主要内容):

  1. GC作用

  2. GC在什么时候

  3. 对谁

  4. 做了什么事情

  5. GC的种类及各自的特点

我们学习语文的时候,经常会遇到总结段落/文章大意的题目,记得当时语文老师是这么说的:同学们应该按照“谁,在什么时候,对谁,做了什么事情”来组织问题的答案。在这里也是一样,问题其实就是要求我们总结概括GC。

下面依次回答上面5个问题:

GC作用:

这个比较简单:在适当时候帮助回收JVM中的“垃圾”,接下来你可以接着说:这句话可以分为以下三个方面回答:什么时候对谁(怎么定义“垃圾”)做了什么(如何回收)——这也就成功将话题向下面三点展开了:

什么时候:

也就是GC会在什么时候触发,主要有以下几种触发条件:

什么意思呢?对象大都在Eden区分配内存,如果某个时刻JVM需要给某一个对象在Eden区上分配一块内存,但是此时Eden区剩余的连续内存小于该对象需要的内存,Eden区空间不足会触发minor GC。触发minor GC前会检查之前每次Minor GC时晋升到老年代的平均对象大小是否大于老年代剩余空间大小,如果大于,则直接触发Full GC;否则,查看HandlePromotionFailure参数的值,如果为false,则直接触发Full GC;如果为true(默认为true,表示允许担保失败,虽然剩余空间大于之前晋升到老年代的平均大小,但是依旧可能担保失败),则仅触发Minor GC,如果期间发生老年代不足以容纳新生代存活的对象,此时会触发Full GC 。

老年代满了,会触发Full GC(回收整个堆内存)。关于老年代:

  1. 分配很大的对象:大对象直接进入老年代,经常出现大对象容易导致内存还有不少空间时就提前触发垃圾收集以获取足够多的连续空间;

  2. 长期存活的对象将进入老年代;

  3. 如果survivor空间中相同年龄所有对象大小的总和大于survivor空间的一半,年龄大于等于该年龄的对象就可以直接进入老年代;

  4. CMS GC在出现promotion failure和concurrent mode failure的时候

上面这三种情况会导致“老年代“满”,会触发full GC。

对谁:

对不再使用的对象,怎么判别一个对象是否还活着呢?这时可以从“引用计数法”讲到“可达性分析算法”。

在“可达性分析算法”中标记为不可达的对象,并非是“非死不可”的,还有回旋的余地。要宣告一个对象死亡,至少要经过两次标记的过程:如果对象在进行可达性分析后发现没有与GC Roots相连的引用链,那它将会被第一次标记并进行筛选,筛选的条件是此对象是否有必要执行finalize方法。如果对象没有覆盖finalize方法或者该方法已经执行过了,则被视为“没有必要执行”,宣告死亡。剩下的对象将被加入一个低优先级的队列中执行finalize方法。这里的执行指的是会触发这个方法,并不保证执行完该方法(只保证虚拟机会触发该方法),否则如该方法存在死循环,该队列就已经卡死了,GC也瘫痪了,所以只保证触发该方法。Finalize是对象逃脱死亡的最后一次机会(可以在finalize方法中重新与引用链上的任何一个对象建立关联)。在触发finalize方法之后,GC将对该队列中的对象进行第二次标记,如果此时该对象仍不在引用链上,该对象就会被回收。如果第二次标记前,该对象成功与引用链上的对象建立了连接,它会被移出“即将回收的集合”,自救成功。注:任何一个对象的finalize方法只会被系统调用一次,即在finalize方法中最多能实现一次自救。另外,finalize方法在jdk9中被标记为“废弃”方法了,不建议使用。

做了什么

不可达的对象,如何被回收:

  1. 标记-清除法:在标记(可达性算法标记)完成后统一回收所有被标记的对象。它是最基础的算法,后续算法都是基于它的不足而改进,主要不足有:效率问题,标记和清除效率都不高;另外一个是空间问题,标记清除后会产生大量不连续的内存碎片,碎片太多可能导致在需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾回收动作。标记清除算法回收后的内存图,如下所示:

    image
  2. 复制算法:为了解决标记清除算法的效率问题,“复制算法”出现了。“复制算法”将可用内存分为大小相同的两部分,每次只使用其中的一块,当使用的那一块内存快用尽时,就将还存活的对象复制到另外一块内存上,然后把已经使用过的内存空间一次性清理掉。这样就是每次都对整个搬去内存进行回收,也不用考虑内存碎片等复杂问题,只需要移动指针,按顺序分配内存即可,实现简单,运行高效。但是代价就是每次只能使用一半的内存,代价有点高。现代商业虚拟机都是采用这种手机算法来回收新生代的。实际上新生代中的对象98%都是“朝生夕死”所以远远用不着每次仅仅使用一般的内存。新生代中将内存划分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor。当回收时,将Eden和使用的Survivor中还存活的对象一次性复制到另外一块Survivor空间上,最后清理掉Eden和刚刚使用过的Survivor空间。Hotspot默认Eden/Survivor=8,即每次可以使用新生代中90%的容量(80%Eden + 10%Survivor),只有10%会被“浪费”。当然我们没法保证每次回收都只有不多于10%的对象存活,当Survivor空间不够用时(超过10%的对象存活),需要依赖其他内存进行分配担保(这里指老年代),放不下的存活对象将进入老年代。

  3. 标记-整理法:复制收集算法在对象存活率较高时就要进行较多的复制操作,效率将会变低。更关键是,如果不想有空间的浪费,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以老年代一般不采用“复制算法”(没有担保人)。根据老年代的特点,提出了“标记-整理法”:标记过程不变,仍使用“可达性分析算法”,标记完后不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存如下图所示:

    image
  4. 分代收集算法:JVM在实际垃圾回收中实际使用的是分代收集算法根据对象存活周期的不同将内存划分为:新生代和老年代。在新生代每次都只有少量对象存活,选用复制算法;老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用标记-整理法或是标记-清理法进行回收。

上面的不同算法在JVM中有不同的垃圾回收器的实现,在JVM中主要有下面几种收集器:

image

新生代收集器有:Serial收集器、ParNew收集器、Parallel Scavenge收集器;老年代收集器:Cocurrent Mark Sweep(CMS)收集器、Serial Old(MSC)收集器、Parallel Old收集器。另外就是G1收集器,G1独自管理整个内存,不再分新生代和老年代了。上图中,如果两个收集器之间有连线,表示他们可以兼容使用;无连线则表示它们不能一起工作(不兼容)。

下面介绍下这几种收集器的特征:

另外,注意:jdk9及更新的版本中默认的G1收集器;jdk8默认收集器:新生代GC:Parallel Scanvage收集器;老年代使用:parallel old收集器(个人感觉这是加分项)


扫描下方二维码,及时获取更多互联网求职面经javapython爬虫大数据等技术,和海量资料分享
公众号菜鸟名企梦后台发送“csdn”即可免费领取【csdn】和【百度文库】下载服务;
公众号菜鸟名企梦后台发送“资料”:即可领取5T精品学习资料java面试考点java面经总结,以及几十个java、大数据项目资料很全,你想找的几乎都有

扫码关注,及时获取更多精彩内容。(博主今日头条大数据工程师)

推荐阅读:

秋招大总结

搞定HashMap所有面试问题(上)

搞定HashMap所有面试问题(下)

搞定ArrayList和LinkedList所有面试问题

上一篇 下一篇

猜你喜欢

热点阅读