JVM的垃圾回收算法

如何确定垃圾

想要回收垃圾,首先要判断对象是否可视为垃圾。确定垃圾的方法有两种,分别是引用计数法可达性分析算法

1.引用计数法
使用引用计数法,要先给每一个对象中添加一个计数器,一旦有地方引用了此对象,则该对象的计数器加1,如果引用失效了,则计数器减1。这样当计数器为0时,就代表此对象没有被任何地方引用。
缺点是会产生循环依赖,如果两个对应互相引用,导致他们的引用计数都不为0,最终不能回收他们。
2.可达性分析算法
在大部分主流语言中都是通过此方法来判断对象是否存活的,这个算法的思想是通过一系列被称为“GC root”的对象作为起始点,从这些节点开始向下搜索,走过的路径叫做引用链。如果一个对象没有通过引用链连接到GC root节点,则证明此对象是不可用的,如下图所示,GC roots 是根节点,凡是能通过引用链连接上GC root 的Object 1,2,3都是被使用的对象。但是Object 4,5,6却不能通过任何方式连接上根节点,因此判定Object 4,5,6为可回收的节点。
要注意的是,不可达对象不等价于可回收对象,不可达对象变为可回收对象至少要经过两次标记过程。两次标记后仍然是可回收对象,则将面临回收。

JVM的垃圾回收算法
可作为可达性分析根节点的一些对象:
虚拟机栈(栈帧中的本地变量表)中引用的对象。
方法区中类静态属性引用的对象。
方法区中常量引用的对象。
本地方法栈中JNI(Java Native Interface)引用的对象。

垃圾回收算法

1.标记清除
首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。
缺点是会产生内存碎片,内存碎片太多可能会导致当程序在以后的运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。而且标记量大时效率比较低。

JVM的垃圾回收算法
2.标记复制
按内存容量将内存划分为大小相等的两块区域。每次只使用其中一块,当这一块内存满后将其中存活的对象复制到另一块上去,然后把该内存中的垃圾对象清理掉。
解决了标记清除中内存碎片的问题,但是内存支持量偏低,只有原来的1/2.
JVM的垃圾回收算法
3.标记整理
结合了以上两个算法。标记阶段和标记清理算法相同,标记后不是清理对象,而是将存活对象移向内存的一端。然后清除端边界外的对象。
JVM的垃圾回收算法
4.分代回收
在结合以上三种算法的综合分析及 JVM 内存对象生命周期的特点,诞生了一种新的垃圾回收算法——分代回收算法。其核心思想是根据对象存活的不同生命周期将内存划分为不同的域,一般情况下将 GC 堆划分为老年代和新生代。
新生代的特点是每次垃圾回收时都有大量垃圾需要被回收,因此采用标记复制算法。一般将新生代划分为一块较大的 Eden 空间和两个较小的 Survivor 空间,每次使用 Eden 空间和其中的一块 Survivor 空间,当进行回收时,将该两块空间中还存活的对象复制到另一块 Survivor 空间中。
当对象在 Survivor 区躲过一次 GC 后,其年龄就会 +1。默认情况下年龄到达 15 的对象会被移到老年代中。老年代的特点是每次垃圾回收时只有少量对象需要被回收因此采用标记整理算法。