垃圾收集器(二)
(注:图片均来自百度图片;文章部分摘自深入理解Java虚拟机——JVM高级特性与最佳实践一书)
上一篇博客中,提到了垃圾收集的相关算法,那些只是内存回收的方法论。那么垃圾收集器的具体实现是怎么的呢?值得一提的是,Java虚拟机规范中对垃圾收集器应该如何实现并没有任何规定,因此不同的厂商、不同版本的虚拟机所提供的垃圾收集器是有很大差别的。
图一:HotSpot收集器
如上图展示,共有7种作用于不同分代的收集器,如果两个收集器之间存在连线,就说明可以搭配使用。虚拟机所处的区域,则表示它是属于新生代还是老生代收集器。
1 Serial收集器
Serial收集器是最基本、发展历史最为悠久的收集器。它是一个使用复制算法且是单线程的收集器,意味着它不仅仅只会使用一个CPU或一条收集器去完成垃圾收集工作,更重要的是它在进行垃圾收集时,必须暂停其它所有的线程工作,直到它收集完成为止。
它是虚拟机运行在Client模式下的默认新生代收集器。优点是:简单高效。
2 ParNew收集器
ParNew收集器其实就时Serial收集器的多线程版本,除了是多线程收集之外,其他与Serial收集器相比并没有太多的创新之处,但它却是许多运行子Server模式下的模拟机中首选的新生代收集器,因为除了Serial收集器外,只有它可以和CMS收集器配合使用。
ParNew收集器可以使用-XX:+UseConcMarkSweepGC来选择默认的新生代收集器,也可以使用-XX:+UseParNewGc选型来强制指定它。
3 Parallel Scavenge收集器
Parallel Scavenge收集器是一个新生代收集器,它也是一个使用复制算法的收集器,又是并行的多线程收集器。
Parallel Scavenge收集器所关注的点与其它收集器是不同的,CMS收集器关注点是尽可能的缩短垃圾收集时用户线程的停顿时间,而 Parallel Scavenge收集器则的目标则是达到一个可控制的吞吐量。
由于与吞吐量关系密切,Parallel Scavenge收集器也经常被称为“吞吐量优先”收集器。该垃圾收集器,是JAVA虚拟机在Server模式下的默认值,使用Server模式后,java虚拟机使用Parallel Scavenge收集器(新生代)+ Serial Old收集器(老年代)的收集器组合进行内存回收。重要的参数有三个,其中两个参数用于精确控制吞吐量,分别是控制最大垃圾收集停顿时间的-XX:MaxGCPauseMillis参数及直接设置吞吐量大小的 -XX:GCTimeRatio参数。另外一个是UseAdaptiveSizePolicy开关参数。
4 Serial Old收集器
Serial Old 收集器是在 TenuredGeneration 老年代上实现收集的,Serial Old 收集器 所使用的垃圾回收算法是标记-压缩-清理算法。在回收阶段,将标记对象越过堆的空闲区移动到堆的另一端,所有被移动的对象的引用也会被更新指向新的位置。该收集器的主要意义也在于给Client模式下的虚拟机使用。
5 Parallel Old收集器
是Parallel Scavenge收集器的老年代版本,用于老年代的垃圾回收,但与Parallel Scavenge不同的是,它使用的是“标记-整理算法”。适用于注重于吞吐量及CPU资源敏感的场合。
使用方式:-XX:+UseParallelOldGC,打开该收集器后,将使用Parallel Scavenge(年轻代)+Parallel Old(老年代)的组合进行GC。
6 CMS收集器
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。CMS是基于“标记-清除”实现的,它的运作过程分为4个步骤,包括:初始标记(CMS initial mark),并发标记(CMS concurrent mark),重新标记(CMS remark),并发清除(CMS concurrent sweep)。其中初始标记、重新标记两个步骤仍然需要“Stop The World”。初始标记仅仅只是标记GC Roots能直接关联的对象,速度很快,并发标记阶段是进行GC Roots Tracing的过程,重新标记则是为了修正并发标记期间用户程序运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记的时间长一些,但是远比并发标记的时间短。
在整个过程中,耗时时间最长的是并发标记和清除标记过程。而且这两个阶段是可以和用户进程一起进行的,因此可以认为CMS收集器的内存回收过程是并发的。
CMS的优点是:并发收集、底停顿,因此也被称为并发低停顿收集器。但是CMS有三个较为明显的缺点
1)CMS收集器对CPU资源非常敏感,因为它是面向并发设计的程序。在并发阶段,它虽然不是导致用户线程停顿,但是会占用一部分线程而导致应用程序变慢,总吞吐量下降。CMS默认启动的回收线程是(CPU数量+3)/4。
2)CMS收集器无法处理浮动垃圾(Floating Garbage),可出现“Concurrent Mode Failure”失败而导致另一次Full Gc的产生。:由于最后的垃圾清除阶段是并发进行的,伴随着程序的运行产生的新的垃圾,在本次收集过程中无法处理掉,指的下次GC时再清理。而且还需要留足够的内存空间给用户线程使用,不能像其他收集器那样等到老年代几乎被填满了再进行收集,需要预留一部分空间提供并发收集时用户线程使用
3)产生大量的空间碎片。CMS是基于“标记-清除”算法实现的收集器。因为这种算法在收集结束后有大量的空间碎片,当碎片过多时,会给大对象的内存
分配带来很大麻烦,往往在老年代中还有很大空间剩余,但是无法找到足够大的连续空间来分配当前对象导致不得不提前触发一次Full GC。
7 G1收集器
Garbage-First(G1,垃圾优先)收集器是服务类型的收集器,目标是多处理器机器、大内存机器。它高度符合垃圾收集暂停时间的目标,同时实现高吞吐量。Oracle JDK 7 update 4 以及更新发布版完全支持G1垃圾收集器。
G1垃圾收集器计划长期替换并发标记清除收集器(CMS,Concurrent Mark-Sweep Collector)。G1和CMS比较,有一些不同点让G1成为一个更好的解决方案。一个不同点是G1是一个压缩收集器。G1收集器充分地压缩空间以完全避免为分配空间使用细粒度的空闲列表,而不是依赖于区块。这相当简化了收集器的部件,和尽量消除可能的碎片问题。同时,G1收集器相比CMS收集器而方言,提供更可预言的垃圾收集暂停时间,允许用户指定想要暂停时间指标。其应用场景为面向服务端应用,针对具有大内存、多处理器的机器、最主要的应用是为需要低GC延迟,并具有大堆的应用程序提供解决方案。
与GC收集器比,它的四个特点是:
1)并行与并发能充分:利用多CPU、多核环境下的硬件优势、可以并行来缩短"Stop The World"停顿时间、也可以并发让垃圾收集与用户程序同时进行;
2)分代收集:收集范围包括新生代和老年代 、能独立管理整个GC堆(新生代和老年代),而不需要与其他收集器搭配、能够采用不同方式处理不同时期的对象、 虽然保留分代概念,但Java堆的内存布局有很大差别、将整个堆划分为多个大小相等的独立区域(Region)、 新生代和老年代不再是物理隔离,它们都是一部分Region(不需要连续)的集合;
3)结合多种垃圾收集算法:空间整合,不产生碎片、从整体看,是基于标记-整理算法、从局部(两个Region间)看,是基于复制算法;这是一种类似火车算法的实现、都不会产生内存碎片,有利于长时间运行;
4)可预测的停顿:低停顿的同时实现高吞吐量、G1除了追求低停顿处,还能建立可预测的停顿时间模型、可以明确指定M毫秒时间片内,垃圾收集消耗的时间不超过N毫秒;
G1收集器的大概的工作过程
1)初始标记,整个过程STW,标记了从GC Roots 的可达对象
2)并发标记 ,真个过程用户线程的垃圾回收线程共同执行,标记出GC Roots可达对象的关联对象,收集整个Region的存活对象。
3)最终标记,整个过程STW,标记出并发标记遗漏的,以及引用关系发生变化的存活对象。
4)筛选回收,垃圾清理过程,如果整个Region没有存活对象,将Region加入到存活列表当中。