因收到Google相关通知,网站将会择期关闭。相关通知内容 06 深入剖析:垃圾回收你真的了解吗?(上) 本课时我们重点剖析 JVM 的垃圾回收机制。关于 JVM 垃圾回收机制面试中主要涉及这三个考题: JVM 中有哪些垃圾回收算法?它们各自有什么优劣? CMS 垃圾回收器是怎么工作的?有哪些阶段? 服务卡顿的元凶到底是谁? 虽然 Java 不用“手动管理”内存回收,代码写起来很顺畅。但是你有没有想过,这些内存是怎么被回收的? 其实,JVM 是有专门的线程在做这件事情。当我们的内存空间达到一定条件时,会自动触发。这个过程就叫作 GC,负责 GC 的组件,就叫作垃圾回收器。 JVM 规范并没有规定垃圾回收器怎么实现,它只需要保证不要把正在使用的对象给回收掉就可以。在现在的服务器环境中,经常被使用的垃圾回收器有 CMS 和 G1,但 JVM 还有其他几个常见的垃圾回收器。 按照语义上的意思,垃圾回收,首先就需要找到这些垃圾,然后回收掉。但是 GC 过程正好相反,它是先找到活跃的对象,然后把其他不活跃的对象判定为垃圾,然后删除。所以垃圾回收只与活跃的对象有关,和堆的大小无关。这个概念是我们一直在强调的,你一定要牢记。 本课时将首先介绍几种非常重要的回收算法,然后着重介绍分代垃圾回收的内存划分和 GC 过程,最后介绍当前 JVM 中的几种常见垃圾回收器。 这部分内容比较多,也比较细。为了知识的连贯性,这里我直接将它们放在一个课时。篇幅有点长,你一定要有耐心学完,也希望你可以对 JVM 的了解上一个档次。 为什么这部分这么重要呢?是因为几乎所有的垃圾回收器,都是在这些基本思想上演化出来的,如果你对此不熟悉,那么我们后面讲解 CMS、G1、ZGC 的时候,就会有诸多障碍。这将直接影响到我们对实践课的理解。 标记(Mark) 垃圾回收的第一步,就是找出活跃的对象。我们反复强调 GC 过程是逆向的。 我们在前面的课时谈到 GC Roots。根据 GC Roots 遍历所有的可达对象,这个过程,就叫作标记。 如图所示,圆圈代表的是对象。绿色的代表 GC Roots,红色的代表可以追溯到的对象。可以看到标记之后,仍然有多个灰色的圆圈,它们都是被回收的对象。 清除(Sweep) 清除阶段就是把未被标记的对象回收掉。 但是这种简单的清除方式,有一个明显的弊端,那就是碎片问题。 比如我申请了 1k、2k、3k、4k、5k 的内存。 由于某种原因 ,2k 和 4k 的内存,我不再使用,就需要交给垃圾回收器回收。 这个时候,我应该有足足 6k 的空闲空间。接下来,我打算申请另外一个 5k 的空间,结果系统告诉我内存不足了。系统运行时间越长,这种碎片就越多。 在很久之前使用 Windows 系统时,有一个非常有用的功能,就是内存整理和磁盘整理,运行之后有可能会显著提高系统性能。这个出发点是一样的。 复制(Copy) 解决碎片问题没有银弹,只有老老实实的进行内存整理。 有一个比较好的思路可以完成这个整理过程,就是提供一个对等的内存空间,将存活的对象复制过去,然后清除原内存空间。 在程序设计中,一般遇到扩缩容或者碎片整理问题时,复制算法都是非常有效的。比如:HashMap 的扩容也是使用同样的思路,Redis 的 rehash 也是类似的。 整个过程如图所示: 这种方式看似非常完美的,解决了碎片问题。但是,它的弊端也非常明显。它浪费了几乎一半的内存空间来做这个事情,如果资源本来就很有限,这就是一种无法容忍的浪费。 整理(Compact) 其实,不用分配一个对等的额外空间,也是可以完成内存的整理工作。 你可以把内存想象成一个非常大的数组,根据随机的 index 删除了一些数据。那么对整个数组的清理,其实是不需要另外一个数组来进行支持的,使用程序就可以实现。 它的主要思路,就是移动所有存活的对象,且按照内存地址顺序依次排列,然后将末端内存地址以后的内存全部回收。 我们可以用一个理想的算法来看一下这个过程。 last = 0 for(i=0;i