分代收集算法
- 没有最优的算法,只有更合适的算法,不同的算法在不同的情况会有不同的效果 所以JVM提出了分代收集算法,它并不是一种算法,而是根据不同时期采用不同的算法。因为前面提到的几种算法,没有一种算法可以完全替代另外一种算法,他们都有各自的优点。
分代收集算法:是基于这样的一个事实:不同的对象的生命周期时不一样的。因此不同生命周期的对象可以采用不同的垃圾回收算法,以便提高垃圾回收效率。一般是把Java堆分成新生代和老年代,这样就可以根据各个代的特点,使用不同的回收算法,以便提高效率。
-
几乎所有的GC目前都是采用的分代收集算法进行垃圾回收的。在HotSpot中,基于分代的概念,GC所用的内存回收的算法,必须结合年轻代和老年代各自的特点。
-
年轻代特点
区域相对老年代较小,对象生命周期短,存活率低,回收频繁。
这种情况的复制算法的回收整理,速度是最快的。复制算法的效率只和当前存活对象的数量有关,因此很适用于年轻代的回收,而复制算法内存利用率不高的问题,通过hotSpot中两个survivor区的设计得到缓解。
- 老年代特点
区域较大,对象生命周期较长,存活率高,回收不及年轻代频繁。
这种情况存在大量存活率高的对象,复制算法明显变的不合适。一般是由标记-清除或者标记-清除与标记-整理混合实现
- Mark阶段的开销与存活对象的数量成正比
- Sweep阶段的开销与所管理的区域大小成正比
- Compact阶段的开销与存活对象的数量成正比
以hotSpot中的CMS回收器为例,CMS是基于Mark-Sweep实现的,对于对象的回收效率很高。而对于碎片问题,CMS采用基于Mark-Compact算法的Serial Old回收器作为补偿措施;当内存回收不佳(碎片导致的Concurrent Mode Failure)时,将采用 Serial Old 执行Full GC 以达到对老年代内存的整理。
- 分代的思想被现有的虚拟机广泛使用。几乎所有的垃圾回收器都有老年代和新生代。
增量收集算法
-
上述现有的算法,在垃圾回收过程中,应用软件处于一种 Stop the world 的状态,在这种状态下,用户所有的线程都会被挂起,暂停一切正常的工作,等待垃圾回收的执行。如果挂起的时间过长,会严重影响用户体验活着系统稳定性。为了解决这个问题,诞生了 增量收集 算法。
-
基本思想
如果一次性将所有的垃圾全部进行处理,需要造成系统长时间停顿,那么可以让垃圾收集线程和用户线程交替执行。每次垃圾收集线程只回收一块区域的内存空间,接着切换到用户线程,依次反复,直到垃圾收集完成。
总的来说,增量收集算法还是 标记-清除、复制 算法。增量收集算法通过对线程冲突的妥善处理允许垃圾收集线程以分阶段的方式完成清理或者复制工作。
- 缺点
使用这种方式,由于在垃圾回收过程中,间断性的还执行了应用程序代码,所以能减少系统的停顿时间。但是,因为线程切换和上下文切换的损耗,会使得垃圾回收总成本上升,造成系统吞吐量的下降。
分区算法
- 一般来说,在相同的条件下,堆空间越大,一次GC所需要的时间就越长,有关GC产生的停顿也越长。为了更好的控制GC产生的时间,将一块大的内存区域分成多个小块,根据目标的停顿时间,每次合理的回收若干个小区间,而不是整个堆空间,从而减少一次GC所产生停顿的时间。
- 分代算法按照对象的生命周期分为两个空间,分区算法将整个堆空间分成若干个不同的小区间。每一个小区间都独立使用独立回收。这种好处就是可以控制一次回收多个小区间。
这些基本的垃圾回收算法只是基本思路,真正的垃圾回收要复杂的多,目前发展的前沿GC都是复合算法,并且并行和并发兼备。