六种主要的垃圾回收算法和思想

Java语言的一大特色就是能够自动进行垃圾回收处理,无需开发人员过于关注系统资源的释放状况。自动垃圾收集虽然大大减轻了开发人员的工做量,可是也增长了软件系统的负担。一个不合适的垃圾回收方法和策略将会对系统性能形成不良影响。算法

1. 引用计数法

引用计数法是最经典古老的一种垃圾收集方法,它的实现也很简单:对于一个对象A,只要有任何一个对象引用了A,则A的计数器就加1,当引用失效时,引用计数器就减1.只要对象A的引用计数器的值为0,则对象A就不可能再被使用。性能

引用计数法实现简单,只须要为每个对象配备一个整型计数器便可。可是,它存在一个很严重的问题,即没法处理循环引用的状况,所以在Java的垃圾回收器中没有使用这种算法。优化

一个简单的循环引用示例以下:
线程

对象A和对象B循环引用,此时他们的引用计数器都不为0,可是在系统中已经找不到第三个对象引用了A或者B,也就是说,A和B应该是被回收的垃圾。可是由于循环引用而没法被识别,最终可能会致使内存泄漏。对象

2. 标记-清除法

标记-清除法是现代垃圾回收算法的基础。内存

它将垃圾回收分为两个阶段:标记阶段和清除阶段。一种可行的实现是:ssl

  1. 在标记阶段,标记全部从根节点出发的可达对象。所以,全部未被标记的对象就是未被引用的垃圾对象。
  2. 在清除阶段,清除全部未被标记的对象。

而标记-清除法可能产生的最大问题就是空间碎片。回收以后的空间不是连续的,不连续的内存空间的工做效率要低于连续的空间,这是标记-清除法最大的缺点。资源

3.复制算法

与标记-清除法相比,复制算法是一种相对高效的回收算法。开发

它的核心思想是:将内存分为两部分,每次只使用其中一部分。在垃圾回收时,将正在使用的内存中的存货对象复制到未使用的内存块中,以后清除正在使用的内存块中的全部对象,交换两个内存的角色,完成垃圾回收。效率

若是系统中的垃圾对象不少,那么复制算法须要复制的存活对象的数量也不会不少,所以当须要使用复制算法时仍是比较高效的。又由于全部对象都会被统一复制到新的内存空间中,因此能够保证回收后的内存空间是没有碎片的。

虽然有以上两大有点,可是复制算法的代价缺点是将系统内存折半。所以单纯的复制算法会让人没法接受。

在Java的新生代串行垃圾回收器中,使用了复制算法。新生代分为eden空间、from空间和to空间三个部分。其中from和to空间能够视为用于复制的两块大小相同、地位相等,且角色能够互换的空间块,它们也被称为survivor空间,即幸存者空间,用于存放未被回收的对象。

在垃圾回收时,eden空间中的存活对象会被复制到未使用的survivor空间中(假设为to),正在使用的survivor空间(假设为from)中的年轻对象也会被复制到to空间中(大对象或者老年对象会直接进入老年代,若是to空间已经满了,则对象也会直接进入老年代)。此时eden和from空间中的剩余对象将都是垃圾对象,能够直接清空,to空间则存放这次回收后存活的对象。

这样改进后的复制算法既保证了空间的连续性,又避免大量内存空间被浪费。

4. 标记-压缩算法

复制算法的高效性是创建在存活对象少,垃圾对象多的前提下。这种状况广泛存在于年轻代,可是在老年代,更常见的状况是大部分对象都是存活对象,若是使用复制算法,因为存活对象多,复制的成本也会很高。

基于老年代垃圾回收的特性,须要使用新的算法,而标记-压缩算法是老年代的一种回收算法,它在标记-清除算法之上作了一些优化。

它和标记-清除算法不一样之处在于:在清除阶段,它会将全部的存活对象压缩到内存的另外一端。以后清理边界以外的全部空间。这种算法既避免了碎片的产生,又不须要两块相同的内存空间,所以性价比较高。

5. 增量算法

对大部分的垃圾回收算法而言,在垃圾回收的过程当中,应用软件的全部线程都会挂起,暂停一切正常工做,等待来回收的完成。若是垃圾回收时间很长,则应用程序会被挂起好久,这会严重影响用户体验和系统稳定性。

增量算法的基本思想就是,让垃圾回收线程和应用线程交替执行,每次只收集一小片区域的内存空间,接着切换应用程序线程。如此往复知道垃圾回收完成。

使用这种方式进行垃圾回收能够减小系统的停顿时间,可是由于线程切换和上下文转换的消耗,会使得垃圾回收的整体成本上升,形成系统吞吐量的降低。

6.分代

前面介绍的几种垃圾回收算法没有哪种能够彻底替代其余算法,它们有各自的优势和缺点。所以,根据垃圾回收对象的特性,使用合适的算法回收,才是明智的选择。

分代就是基于这种思想,它将内存区域根据对象特色分为几块,根据每块内存区间的特色,使用不一样的回收算法,提升垃圾回收的效率。

相关文章
相关标签/搜索