面试的时候按照这个套路回答 Java GC 的相关问题一定能过!

    技术2022-07-10  137

    每天早上七点三十,准时推送干货

    Hello 大家好,我是鸭血粉丝,2020 注定是一个不平凡的一年,很多小伙伴在后台和星球留言都说今年的工作不好找,也有应届生小伙伴给阿粉发消息问阿粉所在的公司今年是否招应届生,阿粉也只能帮小伙伴问问 HR,但是阿粉也做不了主。

    刚好前几天一个小伙伴在微信上问阿粉,说是面试一家公司被问到 Java GC 相关的东西,虽然平时也有准备,但是回答起来总是零零散散,感觉没有逻辑。其实阿粉也能理解,现在面试都是一种问答模式,一个随便问问,一个死记硬背,很多时候面试前准备的好好的一到面试的时候可能紧张就给忘了,事后感觉自己表现的不好。

    这篇文章给大家举个例子,在遇到一个问题或者知识点的时候要怎么去理解和学习。

    Java GC

    目标

    遇到一个问题或者一个知识点,我们要理解和明白是要解决什么问题的。说到 Java GC 那这个 GC 的目的是什么呢?很显然是回收内存,因为内存是有限的,随着程序中创建的对象越来越多,如果进行回收就会导致内存越来越大,最后程序就会出现异常。既然目的是为了回收内存,那么新的问题来了,哪些对象可以被回收呢?什么时候进行回收呢?怎么回收呢?

    哪些对象可以被回收

    简单来说就是无用的对象可以被回收,那么换句话说,如果定义一个对象是无用的呢?这里主要有两种方法,一个叫引用计数法,一个叫可达性分析法。

    引用计数

    引用计数说的是如果一个对象被别的对象进行了一次引用,那么该对象会有一个引用计数器,这个计数器就会加一;如果被释放一下,引用计数器就会减一。当引用计数器的计数为 0 的时候就表示这个对象是无用的,此时就可以对这样对象进行回收了。表面上看好像挺合理的,实现起来也很方便,但是仔细一想就会发现有问题。既循环引用的问题,比如对象 A 引用了对象 B,但是对象 B 当中也引用了对象 A,那么这个时候对象 A 和对象 B 的引用计数器的计数都不会是 0,但是这两个对象都没有被其他对象引用,理论上来说这两个对象都是可以被回收的。

    从上面看到,这种方案是有问题的会导致内存泄露。随之而来的就出现了另一种方案,可就是可达性分析。

    可达性分析

    可达性分析说的是从 GCRoots 的点作为起点,向下搜索,当找不到任何引用链的时候表示该对象为垃圾对象。那么哪些对象可以被认为是 Roots 节点呢?有 Java 栈中的对象,方法区的静态属性和常量以及本地方法栈中的对象。从这几种对象依次向下搜索,如果没有能达到 Roots 节点的对象就是垃圾对象,就说明可以被回收。

    如下图所有,对象 A,B,C都能找到与 Roots 节点的联系,但是对象 D,E,F 三个并不能找到与 Roots 节点的联系,也就是不可达,所以 DEF 这三个对象就是垃圾对象。

    什么时候回收

    上面的两种方案解决了哪些对象能被回收,那么下个问题,就是什么时候进行垃圾回收呢?在排除人为调用的时候,垃圾回收都是发生在为新生对象进行内存分配的时候,这个时候如果内存空间不足就会触发 GC 进行垃圾回收。

    怎么回收

    上面我们知道了哪些对象可以被回收,也知道我们应该什么时候进行回收,那下面要解决的就是如何进行垃圾回收了。垃圾回收根据实现的方式不同有多种不同的算法实现。比如有标记清除算法,复制算法,标记整理算法,分代回收算法,下面简单介绍一下,想深入了解的可以自行去研究一下。

    标记清除算法

    标记清除算法很好理解,主要就是执行两个动作,一个是标记,另一个是对进行标记的对象内存进行清除回收。这个算法有个问题就是会出现内存碎片化严重。如下图所示:

    从上图中可以看到,在进行内存回收后出现了严重的内存碎片化,这就导致在分配某些大对象的时候仍然会出现内存不够的情况,但是总体内存确是够的。

    复制算法

    复制算法的实现方式比较简洁明了,就是霸道的把内存分成两部分,在平时使用的时候只用其中的固定一份,在当需要进行 GC 的时候,把存活的对象复制到另一部分中,然后将已经使用的内存全部清理掉。如下图:

    从上图可以看到解决了标记清除的内存碎片化问题,但是很明显复制算法有另一个问题,那就是内存的使用率大大下降,能使用的内存只有原来的一半了。

    标记整理算法

    既然标记清除和复制算法各有优缺点,那自然的我们就想到是否可以把这两种算法结合起来,于是就出现了标记整理算法。标记阶段是标记清除算法一样,先标记出需要回收的部分,不过清除阶段不是直接清除,而是把存活的对象往内存的一端进行移动,然后清除剩下的部分。如下图:

    标记整理的算法虽然可以解决上面两个算法的一些问题,但是还是需要先进行标记,然后进行移动,整个效率还是偏低的。

    分代回收算法

    分代回收算法是目前使用较多的一种算法,这个不是一个新的算法,只是将内存进行的划分,不同区域的内存使用不同的算法。根据对象的存活时间将内存的划分为新生代和老年代,其中新生代包含 Eden 区和 S0,S1。在新生代中使用是复制算法,在进行对象内存分配的时候只会使用 Eden 和 S0 区,当发生 GC 的时候,会将存活的对象复制到 S1 区,然后循环往复进行复制。当某个对象在进行了 15 次GC 后依旧存活,那这个对象就会进入老年代。老年代因为每次回收的对象都会比较少,因此使用的是标记整理算法。

    垃圾回收器

    讲完了垃圾回收算法,我们再看下垃圾回收器,每一种垃圾回收器都是不同时代的不同产物,都有其独特性。

    Serial 垃圾收集器(单线程、复制算法)

    ParNew垃圾收集器(Serial+多线程)

    Parallel Scavenge 收集器(多线程复制算法、高效)

    SerialOld收集器(单线程标记整理算法)

    ParallelOld收集器(多线程标记整理算法)

    CMS收集器(多线程标记清除算法)

    G1收集器

    各个垃圾收集器的配合使用情况可以参考下图,个人觉得对这么多的收集器没有必要全部精通,可以注重关注一下 CMS 和 G1 就可以了。感兴趣的小伙伴可以自己的研究一下。

    1. 人人都能看懂的 6 种限流实现方案!

    2. 一个空格引发的“惨案“

    3. 大型网站架构演化发展历程

    4. Java语言“坑爹”排行榜TOP 10

    5. 我是一个Java类(附带精彩吐槽)

    6. 看完这篇Redis缓存三大问题,保你能和面试官互扯

    7. 程序员必知的 89 个操作系统核心概念

    8. 深入理解 MySQL:快速学会分析SQL执行效率

    9. API 接口设计规范

    10. Spring Boot 面试,一个问题就干趴下了!

    扫码二维码关注我

    ·end·

    —如果本文有帮助,请分享到朋友圈吧—

    我们一起愉快的玩耍!

    你点的每个赞,我都认真当成了喜欢

    Processed: 0.009, SQL: 9