垃圾收集器与内存分配策略---垃圾收集器
垃圾收集器
收集算法是内存回收的方法论,垃圾收集器是内存回收的具体实现。
以上是 HotSpot 虚拟机中的 7 个垃圾收集器,连线表示垃圾收集器可以配合使用。
- 单线程与多线程:单线程指的是垃圾收集器只使用一个线程,而多线程使用多个线程;
- 串行与并行:串行指的是垃圾收集器与用户程序交替执行,这意味着在执行垃圾收集的时候需要停顿用户程序;并行指的是垃圾收集器和用户程序同时执行。除了 CMS 和 G1 之外,其它垃圾收集器都是以串行的方式执行。
(1)serial收集器
serial是新生代下的单线程收集器,它在进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束。它是虚拟机运行在客户模式下的默认新生代垃圾收集器,它的优点是:简单高效,在单个 CPU 环境下,由于没有线程交互的开销,因此拥有最高的单线程收集效率。
(2)ParNew
ParNew是serial的多线程版本,工作在新生代,是虚拟机在server模式下的首选新生代垃圾收集器,除了serial外只有它能和CMS收集器配合工作。
ParNew在单CPU情况下,不会取得比serial好的收集效果。
(3)paralle scavenge
paralle scavenge是新生代垃圾收集器,也是使用复制算法的收集器,又是并行的多线程收集器。它关注的点和其他收集器不同,CMS等收集器关注的是尽可能的缩短用户线程停顿时间,而它的目标则是达到一个可控制的吞吐率,因此paralle scavenge收集器也被称为“吞吐量优先”收集器。这里的吞吐量指 CPU 用于运行用户程序的时间占总时间的比值。
停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能提升用户体验。而高吞吐量则可以高效率地利用 CPU 时间,尽快完成程序的运算任务,适合在后台运算而不需要太多交互的任务。
缩短停顿时间是以牺牲吞吐量和新生代空间来换取的:新生代空间变小,垃圾回收变得频繁,导致吞吐量下降。
可以通过一个开关参数打开 GC 自适应的调节策略(GC Ergonomics),就不需要手工指定新生代的大小(-Xmn)、Eden 和 Survivor 区的比例、晋升老年代对象年龄等细节参数了。虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量。
(4)serial old
serial old 是serial的老年代版本,它同样是单线程的收集器 ,使用标记-整理算法,这个收集器的主要意义也是个client模式下的虚拟机使用 ,如果实在server模式下使用,那它还有两种用途:
(1)配合paralle scavenge使用。
(2)作为CMS的后背预案,在并发收集Concurrent Mode Failure时使用。
(5)Parallel Old
Parallel Old 时paralle scavenge的老年代版本,使用多线程和标记-整理算法。
在对吞吐量以及CPU资源敏感的场合,优先考虑paralle scavenge和parallel old 收集器。
(6)CMS收集器
cms收集器是一种获取最短回收停顿时间为目标的收集器。cms是基于标记-清除算法实现的。它的运作过程分为4个部分。
(1)初始标记
(2)并发标记
(3)重新标记
(4)并发回收
初始标记,并发标记这两步仍然需要“stop the world”(停止用户线程工作),初始标记仅仅是标记一下GC Roots 能直接关联到的对象,速度很快,并发标记就是进行GC Roots 向下遍历的过程,速度比较慢,重新标记是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记。
并发标记和并发清除这两部分是耗时最长的,但是这两阶段都可以和用户线程一起工作,所以从总体上来说,cms收集器的内存回收过程是与用户线程一起并发执行的。
优点:并发收集,低停顿。
缺点:
(1)虽然不会导致用户线程停顿,但是这是以牺牲了总的吞吐量为代价的,导致cpu利用率不高。
(2)不能够收集浮动垃圾导致full GC产生。由于cms并发清理阶段用户线程仍在执行,伴随着程序的运行还会有新的垃圾产生,这部分垃圾出现在标记之后,cms无法在当次处理他们,只能等到下次GC时在清理。这部分垃圾就称为浮动垃圾。
(3)由于采用标记-清除算法,产生大量的不连续内存碎片,对大对象的分配带来麻烦。往往会出现老年代还有很大空间,但是无法找到连续的空间来分配,不得不提前触发一次Full GC。
(7)G1收集器
G1是一款面向服务器应用的垃圾回收器。HotSpot团队赋予它的使命是代替CMS垃圾收集器,与其他的GC收集器相比,G1具有以下特点:
(1)并行与并发:G1能充分利用多cpu的优势,来缩短stop the world停顿时间,部分其他的GC收集器原本要停止java线程执行GC动作,G1仍然可以通过并发让Java线程继续工作。
(2)分代收集:分代的概念在G1中仍然保留,但是G1可以不用和其他的收集器配合就能独立的管理整个GC堆,能采用不同的方式去处理新建的对象和已经存活了一段时间的对象,熬过多次GC的旧对象以获取更好的收集效果。
(3)空间整合:G1从整体上来看是基于标记-整理算法实现的收集器,从局部上来看(两个region)是基于复制算法实现的收集器,这两种算法都意味着G1在收集的过程中不会产生内存碎片,收集能够提供规整的内存。
(4)可预测的停顿:G1相对于CMS的优势,G1除了追求低停顿时间外,还能够建立可预测的停顿时间模型,能明确让使用者指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒。
G1将整个Java堆分成多个大小相等的独立区域(region)虽然还保留老年代和新生代的概念,但是老年代和新生代不是物理隔离的了。
G1收集器之所以能建立可预测的停顿时间模型,是因为它有计划的避免在整个java堆进行垃圾回收,G1跟踪各个region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据收集时间,优先回收价值最大的区域,这种使用region划分内存空间及有优先级的回收方式,保证了G1收集器在有限的时间内可以获取可能高的收集效率。
每个 Region 都有一个 Remembered Set,用来记录该 Region 对象的引用对象所在的 Region。通过使用 Remembered Set,在做可达性分析的时候就可以避免全堆扫描。
如果不维护remembered set,G1的运作分为以下几个步骤:
(1)初始标记
(2)并发标记
(3)最终标记
(4)筛选回收
初始标记阶段仅仅只是标记一下GC Roots能直接关联到的对象,并且需要修改TAMS的值,让下一阶段用户程序并发运行时,能在正确可用的region中创建对象,这个阶段需要停顿线程,但是停顿时间很短。
并发标记是从GC Roots开始从堆中对象进行可达性分析,找出存活的对象,这阶段耗时较长,但是可以与用户线程并发运行。
最终标记则是为了修正在并发标记期间因用户程序继续运行而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程remembered set 中,这段需要停顿线程,但是可以并行进行。
筛选回收阶段首先对各个region的回收价值和成本进行排序,根据用户所期望的GC时间来指定回收计划。