本文章将着重用图描述引用计数器、复制算法、标记清除法、标记压缩法的实现原理以及过程
引用计数器
对于每一个对象,都会添加一个引用计数器,每当这个对象被引用时,其被引用的次数就会加1,如果对这个对象的引用失效时,那么就会减一。在任何时刻,只要当记录的次数为0时,那么这个对象就可以被回收(如下图所示)
优点:实现简单
缺点:程序计数器本身的创建就会消耗内存,而对每个对象引用次数记录也会影响的性能。因此该方式很少使用
复制算法
了解复制算法的实现过程,则需要了解Jvm堆的分区:
- 伊甸园区:所有的对象都是在伊甸园区创建出来
- 幸存区:在第一次垃圾回收后,没有被回收的对象存入幸存区
- 老年区:在gc对幸存区进行垃圾回收后,依然没有被回收的对象进入老年区
- 永久代/元空间:在JDK1.8之前,该区域被称为“永久代”,JDK1.8即之后,改为元空间。用来存放jdk自身携带的Class对象,Interface元数据,以及Java运行时的一些环境或类信息,这个区域不存在垃圾回收,关闭虚拟机就会释放这个区域的内存。
在新生区中,存在有两个幸存区,这里我们命名为幸存区to和幸存区from。在Jvm实际的运行过程中,复制算法则是通过幸存区to和幸存区from不断的进行转换来进行垃圾回收,以下是执行过程:
- 进行第一次GC前两个幸存区均为空(为了区分,绿色和灰色均为被GC的垃圾对象,但是绿色表示为GC后未被删除的对象,灰色表示为GC后被删除的对象);
- 第一次GC开始,所有被GC的对象进入幸存区,存储垃圾的幸存区为幸存区from,空幸存区为幸存区from
- 幸存区from将幸存对象全部复制并传递至幸存区to中,其他对象则被回收,并且转为幸存区to,幸存区to接受原幸存区from传递过来的幸存对象,并且转为幸存区from。
总结:将原有的内存空间分为两块,每次只使用其中一块,在垃圾回收时,将正在使用的内存中的存活对象复制到未使用的内存块中,之后,清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收。
优点:实现简单且高效,不会产生内存碎片。
缺点:浪费了一半的内存空间用于转换,大大降低了内存的利用率
标记清除法
标记算法主要分为两个阶段:标记阶段和清除阶段
- 标记阶段:首先扫描所有对象,将依旧存活的对象进行标记
- 清除阶段:再次扫描所有对象将未标记的对象删除
优点:没有内存的浪费,无需额外的空间
缺点:对象删除后会产生内存碎片,且扫描了两次,浪费了一定的性能
压缩清除算法
标记算法主要分为两个阶段:标记阶段、清除阶段、压缩阶段
- 标记阶段:首先扫描所有对象,将依旧存活的对象进行标记
-
清除阶段:扫描所有对象,将未标记的对象删除
-
压缩阶段:再次扫描,向一端移动或者的对象
总结:不难得出,压缩清除算法其实是标记清楚法的一种优化,它的主要目的是为了解决代码块的生成。
优点:无需额外空间,且不产生代码块;
缺点:进行了多次扫描,一定程度上浪费了性能
总结
效率上比较:
- 内存效率:复制算法>标记清楚算法>标记压缩算法(时间复杂度)
- 内存整齐度:复制算法=标记压缩算法>标记清楚算法
- 内存利用率:标记压缩算法=标记清除算法>复制算法
使用区间比较:
新生区:
- 特点:存活率低
- 使用复制算法
老年区:
- 特点:区域大,存活率高
- 标记清除+标记压缩混合实现