本文转载自https://blog.csdn.net/wen7280/article/details/54428387
一、垃圾回收的概念
GC中的垃圾,指的是存在于内存中的、不会再被使用的对象。而垃圾回收就是把那些不再被使用的对象进行清除,收回占用的内存空间。如果不及时对内存中的垃圾进行清理,那么这些垃圾对象所占的内存空间会一直保留到应用程序结束,被保留的空间无法被其他对象使用。如果大量不会被使用的对象一致占着空间不放,如果应用程序需要内存空间,没有多余的内存空间供其使用的话,就会导致内存溢出。因此,对内存空间的管理来说,识别和清理垃圾对象是至关重要的。
**
二、垃圾回收的各种算法
**
(1) 引用计数法
引用计数法是最经典的一种垃圾回收算法。其实现很简单,对于一个A对象,只要有任何一个对象引用了A,则A的引用计算器就加1,当引用失效时,引用计数器减1.只要A的引用计数器值为0,则对象A就不可能再被使用。
但是该算法却存在两个严重的问题:
1) 无法处理循环引用的问题,因此在Java的垃圾回收器中,没有使用该算法。
2) 引用计数器要求在每次因引用产生和消除的时候,需要伴随一个加法操作和减法操作,对系统性能会有一定的影响。
注意:由于引用计数器算法存在循环引用以及性能的问题,java虚拟机并未使用此算法作为垃圾回收算法。
(2)标记清除法
标记清除法是现代垃圾回收算法的思想基础。
标记清除法将垃圾回收分为两个阶段:标记阶段和清除阶段。
在标记阶段,首先通过根节点,标记所有从根节点开始的可达对象,因此未被标记的对象就是未被引用的垃圾对象。然后在清除阶段,清除所有未被标记的对象。这种方法可以解决循环引用的问题,只有两个对象不可达,即使它们互相引用也无济于事。也是会被判定位不可达对象。
标记清除算法可能产生的最大的问题就是空间碎片。
(3) 复制算法
算法思想:将原有的内存空间分为两块相同的存储空间,每次只使用一块,在垃圾回收时,将正在使用的内存块中存活对象复制到未使用的那一块内存空间中,之后清除正在使用的内存块中的所有对象,完成垃圾回收。
如果系统中的垃圾对象很多,复制算法需要复制的存活对象就会相对较少(适用场景)。因此,在真正需要垃圾回收的时刻,复制算法的效率是很高的。而且,由于存活对象在垃圾回收过程中是一起被赋值到另一块内存空间中的,因此,可确保回收的内存空间是没有碎片的。(优点)
但是复制算法的代价是将系统内存空间折半,只使用一半空间,而且如果内存空间中垃圾对象少的话,复制对象也是很耗时的,因此,单纯的复制算法也是不可取的。(缺点)
在java中的新生代串行垃圾回收器中,使用了复制算法的思想,新生代分为eden空间、from空间和to空间3个部分,其中from和to空间可以看做用于复制的两块大小相同、可互换角色的内存空间块(同一时间只能有一个被当做当前内存空间使用,另一个在垃圾回收时才发挥作用),from和to空间也称为survivor空间,用于存放未被回收的对象。
**优点:**这种复制算法保证了内存空间的连续性,又避免了大量的空间浪费。
注意:复制算法比较适用于新生代。因为在新生代中,垃圾对象通常会多于存活对象,算法的效果会比较好。
(4) 标记压缩算法
复制算法的高效性是建立在存活对象少、垃圾对象多的情况下,这种情况在新生代比较常见,
但是在老年代中,大部分对象都是存活的对象,如果还是有复制算法的话,成本会比较高。因此,基于老年代这种特性,应该使用其他的回收算法。
标记压缩算法是老年代的回收算法,它在标记清除算法的基础上做了优化。
(5) 分代算法
分代算法思想:将内存空间根据对象的特点不同进行划分,选择合适的垃圾回收算法,以提高垃圾回收的效率。
新生代的特点是:对象朝生夕灭,大约90%的对象会很快回收,因此,新生代比较适合使用复制算法。
老年代的存活率是很高的,如果依然使用复制算法回收老年代,将需要复制大量的对象。这种做法是不可取的,根据分代的思想,对老年代的回收使用标记清除或者标记压缩算法可以提高垃圾回收效率。
注意:分代的思想被现有的虚拟机广泛使用,几乎所有的垃圾回收器都区分新生代和老年代。
(6) 分区算法
算法思想:分区算法将整个堆空间划分为连续的不同小区间,
如图所示:
每一个小区间都独立使用,独立回收。
算法优点是:可以控制一次回收多少个小区间
通常,相同的条件下,堆空间越大,一次GC所需的时间就越长,从而产生的停顿时间就越长。为了更好的控制GC产生的停顿时间,将一块大的内存区域分割成多个小块,根据目标的停顿时间,每次合理的回收若干个小区间,而不是整个堆空间,从而减少一个GC的停顿时间。