JVM(十三)分代收集、增量收集以及分区算法
1 分代收集算法
前面的所有算法中,没有一种算法能够完全替代其他算法,它们都有自己独特的优势和特点,分代收集算法应运而生:
-
分代收集算法对不同生命周期的对象采取不同的收集方式,一般划分为新生代和老年代,以便提高回收效率
在Java程序运行的过程中,会产生大量的对象,其中有一些对象是与业务信息相关,比如HTTP请求中的Session对象、线程、Socket连接,这类对象与业务直接挂钩,因此生命周期较长
而还有一些对象,主要是程序运行过程中产生的临时变量,这类对象的生命周期较短,比如String对象,由于不可变性系统会产生大量的这些对象,有些甚至只用一次即可回收
-
目前几乎所有的GC垃圾回收器都是采用
分代收集算法
(Generational Collection)执行垃圾回收的:-
年轻代
:区域相对老年代小,对象生命周期短、存活率低(朝生夕死);而复制算法的效率只和当前存活对象的大小有关,因此年轻代采用复制算法进行垃圾回收,复制算法内存利用率不高的问题,通过两个幸存者区的设计得到了缓解默认情况下,新生代和老年代的大小比例为1:2,而新生代中,伊甸园区:幸存者0区:幸存者1区为1:1:8,因此复制算法“浪费”的空间大小仅占内存的1/30,通过这样的设计得到了缓解
-
老年代
:区域较大,对象的生命周期长、存活率高,回收不及年轻代频繁,这种情况存在大量存活率高的对象,复制算法明显变得不合适,一般由标记-清除
或者标记-清除
和标记-压缩
算法的混合实现- Mark
标记阶段
的开销与存活对象的数量成正比 - Sweep
清除阶段
的开销与所管理区域的大小成正比 - Compact
压缩整理阶段
的开销与存活对象的数据成正比
以HotSpot的CMS收集器为例,CMS是基于Mark-Sweep实现的,对于对象的回收效率很高,对于碎片问题,CMS采用基于Mark-Compact算法的Serial Old回收器作为补偿措施:当内存回收不佳(内存碎片问题导致Concurrent Mode Failure)的时候,将采用Serial Old执行Full GC以达到对老年代内存的整理
- Mark
-
2 增量收集算法
上面的现有算法,在垃圾回收的过程中,会使应用软件处于一种Stop The World
的状态,所有的线程都会被挂起,暂停一切正常工作等待垃圾回收的完成,时间一长就会严重影响用户体验和系统的稳定性,增量收集算法
则解决了这个问题:
-
增量收集算法
让垃圾收集线程和应用程序线程交替执行,每次垃圾收集线程都只收集一小片区域的内存空间,接着切换为应用程序线程,依次反复直到垃圾收集完成 -
总的来说,
增量收集算法
的基础仍是传统的标记-清除
和复制算法
,增量收集算法
对线程间的妥善处理,允许垃圾收集线程以分阶段的方式完成标记、清理或复制工作 -
优点:使用这种方式,在垃圾回收过程中,间歇性地还执行了应用程序代码,所以能够减少系统的停顿时间。
-
缺点:因为线程的切换和上下文的消耗,会使得垃圾回收的成本上升,造成系统吞吐量的下降
3 分区算法
- 一般来说,在相同条件下,堆空间越大一次GC所需要的时间就越长,有关GC的停顿也就越长。为了更好地控制GC产生的停顿时间,将一块大的内存区域分割成多个小块,根据目标的停顿时间(分配给垃圾回收线程的时间),每次合理地回收若干个小区间,而不是整个堆空间,从而减少一次GC所产生的停顿
- 分代收集算法按照对象的生命周期长短划分成两个部分,分区算法则将整个堆空间划分成连续的不同小区间
- 每一个小区间都独立使用,独立回收,这种算法的好处就是可以控制一次回收多少个小区间