主要的垃圾回收算法
一、引用计数器
引用计数器实现很简单,对于一个对象A,只要有任何一个对象引用了A,则A的引用计数器就加1。当引用失效时,引用计数器就减1。只要对象A的引用计数器的值为0。则对象A就不可能再被使用了。只要为每个对象配备一个整型的计数器即可。但是计数器有个一个严重的问题,即无法处理循环引用的情况。因此在java的垃圾回收器中,没有使用这个算法。
二、标记-清除算法
标记-清除算法是现代垃圾回收算法的思想基础。标记-清除算法将垃圾回收分为两个阶段:标记阶段和清除阶段。标记阶段,标记出所有需要回收的对象,在标记完成后,统一回收所有被标记的对象。后续的算法都是基于这种思路并对其不足进行改进的。它的主要缺点有两个:1、一个是效率问题,标记和清除两个过程的效率都不高。2、另一个是空间问题,标记清除后,空间是不连续,的,产生大量的内存碎片,空间碎片太多会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而导致提前触发垃圾回收动作。
三、复制算法
与标记-清除算法相比,复制算法是一种相对高效的回收方法。它的核心思想是:将原有的内存空间分为两块,每次只使用其中一块,在垃圾回收时,将正在使用的内存中的存活对象复制到未使用的内存块中,之后,清除正在使用中的内存块的所有对象,交换两个内存的角色,完成垃圾回收。如果系统中的垃圾对象很多,复制算法需要复制的存活对象数量并不会太大。因此,在真正需要垃圾回收的时刻,复制算法的效率还是很高的。又因为对象是在垃圾回收过程中统一被复制到新的内存空间中,因此,可确保回收后的内存空间没有碎片。但是仍然有个缺点,就是这个算法,要将系统内存折半。
在java的新生代串行垃圾回收器中,使用了复制算法的思想。新生代分为eden空间,from空间和to空间三个部分,其中from和to空间可以视为用于复制的两块大小相同、地位相等,且可以进行角色互换的空间块。from和to空间也称为survivor空间,即幸存者空间,用于存放未被回收的对象。
在垃圾回收时,Eden空间中的存活对象会被复制到未被使用的survivor空间中(假设to,如图s1),正在使用的survivor空间(假设from,如图s0)中的年轻对象也会被复制到to空间中(如图s1)(大对象,或者老年对象会直接进入老年代,如果to空间已经满了,对象也会直接进入老年代),此时eden空间和from空间中的剩余对象就是垃圾对象,可以直接清空,to空间则存放此次回收后的存活对象。这种复制算法,既可以保证空间的连续性,又避免了大量的内存空间浪费。
复制算法比较适合新生代,因为新生代,存活对象比较少,复制算法会 比较好。如果存活对象较多的时候,就要进行较多的复制操作,效率会变低,更关键是,如果不想浪费50%的空间,就需要额外的空间进行分配。所以老年代一般不适合复制算法,接下来说的标记-整理算法,适合老年代进行回收。
四、标记-整理算法,也叫标记-压缩算法,这种算法适用于老年代回收,它是在标记-清除算法基础上进行了优化,首先也是需要从根节点开始,对所有要回收的对象进行标记,但是之后,不是清理要回收的对象,而是将所有存活的对象压缩到内存的一端,之后,清理边界外所有的所有的空间。这种方法既避免了碎片的发生,又不需要两块相同的内存空间,因此性价比高。
五、分代收集算法
当前虚拟机垃圾收集都采用分代收集算法,这种算法不是什么新的思想,只是根据对象存活周期的不同将内存进行分块,新生代每次垃圾收集时发现大批对象死去,只有少量存活,所以只要付出少量存活对象的复制成本就可以完成收集,所以适用于复制算法。老年代中因为对象存活率高、没有额外空间对它进行分配担保,所以适合于标记-清除算法或是标记-整理算法来回收。