垃圾收集器与内存分配策略之篇二:垃圾收集器

Posted 烟尘

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了垃圾收集器与内存分配策略之篇二:垃圾收集器相关的知识,希望对你有一定的参考价值。

五、垃圾收集器

   如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。由于java虚拟机规范对垃圾收集器实现没有任何的规范因此不同的厂商,不同的版本的虚拟机所提供的垃圾收集器都有可能会有很大的区别,并且一般都会提供参数供用户根据自己的应用特点和要求组合出各个年代所使用的收集器。

虚拟机中所包含的垃圾收集器如下图:

 

 

 

连线代代表他们可以组合使用。下面分别对以上垃圾收集器进行说明:

01)Serial 是历史悠久的收集器,在垃圾回收期间或中断用户线程,是一个单线程的收集器,在进行垃圾收集的时候暂停其他所有的工作线程,直至他收集结束。由于它是在用户不可见的时候暂停线程,这对许多用户来说都是不可接受的。适合于单个CPU,单线程的情况下面,如果在桌面运行程序下面 即Client模式下面虚拟机来说是一个很好的选择,因为停顿时间很小。是新生代收集器

Serial 收集器的运行过程

02)ParNew 收集器

  Parnew收集器其实是serial收集器的多线程版本,除了可以使用多线程进行垃圾收集以外,其余行为包括Serial收集器的可用的所有控制参数。Parnew收集器的运行过程示例图:

 

需要注意的是除了Serial收集器以外,只有ParNew收集器才能与CMS收集器一起工作。ParNew在单核CPU的情况下面绝对不会有比Serial收集器更好的效果,甚至由于存在线程的开销,该收集器在通过超线程技术实现的两个CPU的环境汇都不能百分百的超越serial收集器。当然随着CPU数量的增加,他对于GC时系统资源的有效利用还是很有好处的。

两种概念的解释:

并发:指用户线程与垃圾收集线程同时执行(不一定是并行的,可能是交替执行),用户线程在继续运行,而垃圾收集程序运行于另一个CPU上面。

并行:指多条垃圾收集线程并行执行,但此时用户线程处于等待状态。

03)  Parallel Scavenge收集器

Paraller Scavenge收集器是一个新生代收集器,他也是采用复制算法的收集器,又是并行的多线程收集器。他与ParNew收集器最大的不同是Parallel Scavenge收集器要达到一个可控的吞吐量。吞吐量= 运行用户代码的时间/(运行用户代码时间+垃圾收集器时间)。如:虚拟机共运行了100分钟,垃圾收集用了1分钟,那么吞吐量是99%。停顿时间越短,用户的体验就会更好。高的吞吐量可以高效率的利用CPU的时间,尽快的完成程序的运算任务,主要适合在后台不需要交互的任务。

 Parallel Scavenge 收集器提供了两个参数用于精确的控制吞吐量,分别是最大垃圾收集停顿时间的-XX:MaxGcPauseMills 参数以及直接设置吞吐量大小的-XX:GCTimeRatio参数。MaxGcPauseMills参数是一个大于0的毫秒数,收集器将尽量地保证内存回收花费的时间不超过设定值。如果把停顿时间调小,会导致GC次数频繁,吞吐量会下降。

GCTimeRatio参数的值应当是一个大于0且小于100的整数,也就是垃圾收集时间占总数的时间的比率,相当于是吞吐量的倒数。因此Paraller Scavenge收集器也别成为吞吐量优先的收集器。Paraller Scavenge收集器还有一个参数-XX:+UseAdapterSizePolicy值得关注,这是一个自适应策略的参数,一旦打开了以后,就不需要尽进行手动的指定新生代大小,Eden和Survivor区域的比例等细节参数了。虚拟机会根据当前的系统信息动态的调整这些参数,成为GC自适应的调节策略。

04)Serial Old收集器

Serial Old收集器是老年代版本,他同样是一个单线程收集器,这个收集器的主要意义是主要是Client模式下面的虚拟机使用。Serial Old收集器使用的是标记整理算法。用途是:在JDK1.5的版本之前与Paraller Scavenge 收集器搭配使用,另一种用途是CMS收集器的备选方案。

05) Parallel Old 收集器是Paraller Scavenge 收集器的老年大版本,使用多线程和标记整理算法。因为新生代收集器Parallel Scavenge 收集器无法与CMS收集器一起工作,所以如果Parallel Scavenge 收集器选择了在新生代使用,那么老年代只能使用Parallel  Scavenge收集器。Parallel Old 收集器只能和Parallel Scavenge收集器一起工作搭配。Serial和 ParNew收集器无法与Parallel Old收集器一起工作。

06)CMS 收集器

CMS 收集器是一个以获取最短回收停顿时间为目标的收集器。目前很大一部分的java应用集中在互联网网站或者B/S系统的服务端上,这类应用尤为重视服务的响应速度,希望停顿时间最短,已给用户最好的体验。CMS收集器就非常符合这类应用的需求。

CMS收集器是基于标记清除算法实现的,它的运作过程相对于前面的几种的收集器来说更复杂一些。整个过程分为初始标记、并发标记、重新标记、并发清除。其中初始标记和重新标记这两个步骤任然需要“stop the  world“。初始标记仅仅只是标记一下GC Roots 能直接关联到的对象,速度很快.并发标记阶段就是进行GC Root Tracing的过程,而重现标记阶段则是为了修正并发标记期间因用户程序运作而导致的那一部分对象的记录,这个阶段的停顿时间一般会比初始标记的时间稍长一些,但远比并发标记时间短。由于整个过程中耗时最长的并发标记和并发清除收集线程都是可以与用户线程一起工作,所以,从总体来说,CMS收集器的内存回收过程是与用户线程一起并发执行的。通过下图可以清楚的看出CMS收集器的运行过程和需要停顿的时间:

CMS是一款优秀的收集器,它的主要优点在名字上面已经体现出来了:并发收集、低停顿,sun公司的一些官方文档中也称之为并发低停顿收集器。但是CMS收集器还远达不到完美的程度,他有以下三个明显的缺点:

 1.CMS收集器对CPU资源特别敏感。其实,面向并发设计的程序都对CPU资源比较敏感。在并发阶段,他虽然不会导致用户线程的停顿,但是会为了占用了一部分线程而导致应用程序变慢,总吞吐量会降低。CMS默认启动的回收线程数量是(CPU数量+3)/4 ,也就是当CPU在4个以上的时候,并发回收垃圾收集线程不少于25%的CPU资源,并随着CPU的数量的增加而下降。但是当CPU不足4个的时候,比如说两个的时候CMS收集线程对用户的影响就会很大,如果本来负载就比较大的时候,还分出一部分去执行垃圾收集线程,就可能导致用户线程执行变慢,这让人很难接受。为了解决这一个问题,虚拟机提供了一种增量式并发收集器,思想是:在并发标记、清理的时候让GC线程、用户线程交替执行,尽量减少GC线程独占资源的时间,这样整个垃圾收集的过程会变得更长,但是对用户程序的影响就会小一些,也就是速度下降没有那么明显。

2.CMS收集器无法处理浮动垃圾。由于CMS并发清理阶段是在和用户线程一起执行的,伴随着程序的运行自然就还会新的垃圾产生,这一部分垃圾在出现在标记过程之后的话,CMS无法在当次收集过程中进行处理,只好在下一次的垃圾清理的时候在进行清理,这一部分垃圾成为浮动垃圾。

3.CMS垃圾收集器是基于的标记清除算法,收集结束后会有大量的空间碎片产生。空间碎片过多的时候,将会给大对象分配带来很大的麻烦,往往会出现老年代还有很多的剩余,但是无法找到足够大的连续的空间来分配当前的对象,不得不提前触发一次Full  GC 。为了解决这个问题,CMS收集器提供了一个 -XX:+UseCMSCompactAtFullCollection开关参数(默认就是开启的),用于在CMS收集器顶不住的时候,进行FullGC时候进行内存碎片的合并过程,内存整理过程是无法并发的,空间碎片问题没有了,但是停顿时间会变得很长。为此虚拟机提供了另一个参数 -XX:CMSFullGCsBeforeCompaction,这个参数是用于设置执行多少次不压缩FullGC,来执行一次带压缩的(默认为0,表示每次full Gc 都进行内存整理)

07) G1收集器

G1是一款面向服务端应用的垃圾收集器。HotSpot开发团队赋予他的使命是(在比较长的时间)未来可替换jdk1.5的CMS收集器。与其他收集器相比,G1收集器的特点:

并行与并发:G1能充分利用多CPU、多核环境下的硬件优势,使用多个CPU来缩短stop-the-world停顿的时间,部分其他收集器原本需要停顿java线程执行的GC动作,G1收集器仍然可以用并发的方式让java线程继续运行。

分代收集:与其他收集器一样,分代概念在G1收集器中得已保留。可以采用不同的处理方式去处理新创建的对象和已经存活了一段时间的对象。

空间整合:G1收集器是基于标记整理算法实现的垃圾收集器,不会产生空间碎片。

可预测性停顿:这是G1相对于CMS的另一个优势。G1除了追求低停顿以外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集收集上的时间不超过N毫秒,这个几乎是已经是实时java(RTSJ)的垃圾收集器的特征了。

 

在G1之前的其他收集都是在老年代和新生代之间进行来及回收的,而G1不在是这样。使用G1收集器的话,java的堆内存布局就与其他的收集器有很大的差别,通它将整个java堆划分为多个大小相等的独立区域,虽然还保留着老年代和新生代的概念,但是新生代和老年代不在是物理隔离了,他们都是一部分Region集合。

G1收集器之所以能建立起来可预测的停顿时间模型,是因为它可以有计划的避免在整个java堆中进行全区域的垃圾回收。G1跟踪各个region里面的垃圾堆价值大小,在后台维护一个优先列表,每次都根据允许的收集时间,优先回收价值最大的Region。这种使用Region划分内存空间以及有优先级的区域回收方式,保证了G1收集器在有限的时间内可以获取尽可能高的收集效率。

   在G1收集器中,Region之间的对象引用以及其他收集器的新生代与老年代之间的对象引用,虚拟机都是采用Rememberd Set 来避免全局扫描的。G1中每一个Region都有一个与之对应的Remember Set,虚拟机发现程序在对Reference 类型的数据进行写操作的时候,会产生一个Write Barrier 暂时中断写操作,检查Reference 引用的对象是否处于不同的Region中(在分代收集的时候,是检查是否老年代中的对象引用了新生代的对象),如果是,便通过CardTable 把相关的引用信息记录到被引用对象的所属的Region的Remember Set之中。当进行垃圾回收的时候,把GC根节点的枚举范围中加入Rememeber Set即可保证不对全局扫描也不会有遗漏。

初始标记阶段仅仅只是标记一下GC ROOTS能关联的对象,并且能修改TAMS的值,让下一阶段用户程序并发运行时,能在正确可用的Region中创建新的对象,这阶段需要停顿线程,但耗时很短。并发标记阶段是从GC ROOTS开始对堆中进行可达性分析研究,找出存活的对象,这阶段耗时较长,但是可以与用户线程并发执行。而最终标记阶段是为了修正正在并发标记标记期间因为用户线程继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段变动记录在线程Remember Set Logs 里面,最终标记阶段需要把Remember Set Logs 的数据合并到Remember Set中,这个阶段需要停顿线程,但是可以并行执行。最后筛选回收阶段首先对各,个Region 的回收价值和成本进行排序,根据用户期望的GC 停顿时间来制定回收计划。

 

以上是关于垃圾收集器与内存分配策略之篇二:垃圾收集器的主要内容,如果未能解决你的问题,请参考以下文章

垃圾收集器与内存分配策略之篇三:理解GC日志和垃圾收集器参数总结

垃圾收集器与内存分配策略之垃圾收集器

垃圾收集器与内存分配策略之垃圾收集算法

垃圾收集器与内存分配策略之内存分配与回收策略

垃圾收集器与内存分配策略---垃圾收集算法

垃圾收集器与内存分配策略