JVM三种垃圾收集算法思想及发展过程

JVM垃圾收集算法的具体实现有很多种,本文只是介绍实现这些垃圾收集算法的三种思想和发展过程。所有的垃圾收集算法的具体实现都是遵循这三种算法思想而实现的。

1.标记-清除算法

标记-清除(Mark-Sweep)算法是最基础的垃圾收集算法。正如其名字描述的那样,该算法分为两个阶段:“标记”和“清除”。首先标记出所有可以被回收的对象,然后经过一轮垃圾回收将所有被标记的对象的内存空间释放,即清除可被回收的对象。标记-清除算法的执行过程如下图所示:

JVM三种垃圾收集算法思想及发展过程_第1张图片

该算法的优点是逻辑简单,最初想到的垃圾收集算法就是这种。之后的收集算法都是针对该算法的不足进行改进而产生的新算法。该算法主要存在两种不足:一是标记和清除两个阶段都比较低效;二是这种算法虽然收回了内存,但是容易导致内存中有大量的不连续的小的内存区域(内存碎片)。当有一个大的对象需要创建时,虽然总的可用内存很多,但是没有一个连续的大的内存空间来存放大对象。

2.复制算法

为了解决标记-清除算法效率低以及内存碎片的问题,出现了复制(copying)算法。该算法的思想是将整个内存空间分为两个大小相同的空间。同一时期只在一个内存空间上创建对象,当该部分内存满了之后,执行垃圾回收。垃圾回收的过程:首先将不能回收的依然可用的对象复制到另一个内存空间上,然后将之前的一个内存空间直接清空。经过该轮垃圾回收后,之前的一个内存空间对象被全部清空了,活着的对象被复制到了另一个内存空间上,此时再创建对象就在另一个内存空间上分配空间了。复制算法的执行过程如下图所示:

JVM三种垃圾收集算法思想及发展过程_第2张图片

该算法的优点是逻辑简单,执行高效。但缺点是实际只使用了一半的可用内存,浪费了一半的内存。在商业的JVM中一般使用复制算法来回收新生代的内存空间。因为新生代中的对象大部分都是使用一次就被抛弃的对象,所以新生代中的对象的存活率是很低的。绝大多数情况下符合80:20的原则。复制算法的具体实现上,是将新生代内存区域划分为8:1:1的三块区域,8的区域叫做Eden区,两个1都叫做Survivor区,分配内存时,首先在一个Survivor区和Eden区分配,当垃圾回收时,将上述两个区的活着的对象复制到另一个Survivor区,然后将Eden区和原来的那个Survivor区清空。这样的话使用了整个可用内存的90%,大大减小了浪费。当然,会出现垃圾回收时一个Survivor区装不下所有存活对象的情况(很少发生),当出现时,就会进行分配担保,由其他内存区域(一般是老年代)来存放溢出的对象。

3.标记-整理算法

复制算法对于对象存活率很低的情况是高效的,但是当对象的存活率非常高时,就变得非常低效了。在老年代中,对象的存活率很高,所以不能使用复制算法。于是根据老年代的对象特点,提出了标记-整理(Mark-Compact)算法。标记-整理算法也分为两个阶段:标记和整理。第一个阶段与标记-清除算法一样:标记出所有可以被回收的对象。第二个阶段不再是简单的清除无用对象的空间,而是将后面的活着的对象依次向前移动。将所有的活着的对象都移动成内存空间中前段连续一个区域,之后的连续的区域都是可分配的没有使用的内存空间。标记-整理算法的执行过程如下图所示:

JVM三种垃圾收集算法思想及发展过程_第3张图片

4.小结:分代收集思想

当前商业虚拟机中一般采用“分代收集算法”。分代收集算法是根据对象的特点将内存空间分成不同的区域(即不同的代),对每个区域使用合适的收集算法。在JVM中一般分为新生代和老年代,新生代中对象的存活率比较低,使用复制算法简单高效;在老年代中,由于对象的存活率较高,所以一般采用标记-整理算法。
对于哪种算法最好,我只能说,没有最好的算法,只有最适合某一种场景的某一种算法。正是因为垃圾收集的对象各有特点,才诞生了多种不同的收集算法。就像语言一样,没有必要去争论Java最好,C++最强。每一个语言的存在必然有其使用场景和适合的地方。兼听则明,多多学习就是了。

参考:《深入理解Java虚拟机-JVM高级特新与最佳实践》第二版 周志明著
图片来自:http://blog.csdn.net/java2000_wl/article/details/8022293

你可能感兴趣的:(jvm,内存,垃圾收集算法,jvm垃圾收集算法)