漫谈jvm垃圾收集(2)
上一篇博文中我们说了,哪些对象该被回收已经在程序运行到什么地方进行回收。接下来我们就来讨论怎么回收这些不在使用的对象。
(1)常用的几个垃圾收集算法
- 标记-清除
从GC Roots开始标记所有被引用的对象,之后遍历整个回收区删除标记的对象
缺点:标记和删除的效率都挺低;并且会产生大量内存碎片
- 复制算法
把内存分为两块,只使用其中一块,回收时先把存活的对象复制到另一块内存,之后清空前一块已经使用的内存。
优点:简单、高效
缺点:需要浪费一定的内存
- 标记-整理
先标记存活对象,之后将所有存活对象都移动到一边,最后清除掉边界之外的内存。
这种回收算法适合回收前内存中存在大量对象的情况,对应就是分代收集策略中的老年代。
目前大多数商业使用的jvm,都是根据对象存活周期的不同,将堆区分为新生代和老年代,同时老年代为新生代提供内存分配担保,大量“朝生夕死”的对象一般都放到新生代,由于新生代的大部分对象都会在一次Minor GC中死亡,存活的对象很少,所以新生代的GC收集器都采用了复制算法。新生代分为Eden 、 S0 、 S1区,其中S0、S1称为Surivivor区。 S0和S1就是用来实现复制的,在任何一次Minor GC后,S0和S1总是只有一个区域有数据,另一个区域为空,以便于下一次复制使用,大对象或者新生代无法容下的对象晋升到老年代。
(2)垃圾收集器
HotSpot虚拟机的垃圾收集器如下图所示:
- Serial 是新生代的串行收集器
它使用的是复制算法,在执行垃圾收集时全程stop the world,只使用一个线程去完成垃圾收集工作。
- Serial Old是Serial在老年代的版本
它使用的是标记-整理算法,同样在执行垃圾收集时全程stop the world,只使用一个线程去完成垃圾收集工作。
同时这个收集器在老年代还有一个重要用途,就是作为CMS收集器在发生Concurrent Mode Failure时的后备收集器。
- ParNew收集器
这是新生代收集器,他也是使用复制算法,在执行垃圾收集时全程stop the world,只不过他使用多个线程去完成垃圾收集工作,其他属性几乎都与Serial收集器一样,只不过是Serial的多线程版本。
- Parallel Scavenge收集器(吞吐量优先收集器)
这是新生代收集器,使用复制算法,同时又是多线程收集器,看起来很像ParNew,但是Parallel Scavenge的关注点不同,其他收集器都是尽可能的把stop the world的时间压缩的尽量短,而Parallel Scavenge的目标是能达到一个可以控制的吞吐量,吞吐量=用户运行代码的时间/(用户运行代码的时机+GC线程运行时间)。对于计算密集型的应用可能会考虑计算的吞吐量,这时候可以使用Parallel
Scavenge收集器来保证吞吐量。
- Parallel Old收集器
这是老年代收集器,使用标记-整理算法,多线程执行垃圾收集。它是Parallel Scavenge的老年代版本。
- CMS收集器(Concurrent Mark Sweep)并发收集、低停顿
这是老年代收集器,使用标记-清除算法,多线程收集器。它执行垃圾收集的整个过程图示如下:
它主要分为四个阶段:
初始标记:stop the world,gc线程仅仅只是标记一下GC Roots直接关联的对象,速度很快
并发标记:GC线程与用户线程并发执行,这期间对象的引用关系可能再次发生变化,所以需要再次下阶段的重新标记
重新标记:stop the world,修正并发标记期间导致的引用关系变化
并发清除:GC线程与用户线程并发执行,GC线程会抢占一部分CPU资源,导致用户程序变慢。
CMS执行一次垃圾回收,会出现2次 stop the world。
由于在并发清理阶段用户线程还在运行,所以CMS收集器不能等老年代都填满了再进行收集,它需要预留出一定的空间给用户线程运行,可以通过-XX:CMSInitiatingOccupancyFraction参数来指定,当老年代使用了百分之多少的时候执行并发清理。
但是如果老年代预留的空间太小,不能满足用户线程分配空间的需求,那么这时候就会发生Concurrent Mode Failure 失败。这是虚拟机会启用Serial Old 收集器stop the world,暂停用户线程执行Full GC,这样性能会大大降低。
由于CMS使用了标记-清理算法,这会产生内存碎片,CMS提供了两个相关参数用于优化内存碎片现象,
-XX:UseCMSCompacAtFullCollection在Full GC时执行内存碎片整理,
-XX:CMSFullGCsBeforeCompaction执行多少次不压缩的Full GC之后,执行一次带压缩的Full GC
总结一下:
(1)Serial, ParNew, Parallel Scanvange, Parallel Old, Serial Old全程都会Stop the
world,JVM这时候只运行GC线程,不运行用户线程。
(2)CMS GC线程可以和用户线程并执行,所以Web应用是使用CMS收集器的一个重要场景
(3) Parallel
Scanvange, Parallel Old是注重吞吐量优先的收集器,如果是计算密集型的服务可以考虑选择它们。