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

五、垃圾收集器

如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。由于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 停顿时间来制定回收计划。

时间: 2024-10-18 17:57:33

垃圾收集器与内存分配策略之篇二:垃圾收集器的相关文章

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

一.GC日志片段如下: [GC[DefNew: 2658K->371K(4928K), 0.0038671 secs] 2658K->2419K(15872K), 0.0173438 secs] [Times: user=0.00 sys=0.00, real=0.02 secs] [Full GC[Tenured: 2048K->370K(10944K), 0.0331593 secs] 4564K->370K(15872K), [Perm : 176K->176K(122

垃圾收集器以及内存分配策略

垃圾回收 垃圾回收的三个问题: 哪些内存需要回收? 什么时候回收? 如何回收? 1.哪些对象需要回收? 判断对象是否存活的办法: 引用计数算法:给对象中添加一个引用计数器,有一个地方引用就+1,引用失效就-1.只要计数器为0则对象已死. 优点:简单易实现: 缺点:无法解决对象之间相互引用的问题.(JVM也因为此种原因没有使用它) 根搜索算法: 通过选取出一个GC Roots对象,已它作为起始点,如果对象不可达,则对象已死. GC Roots对象: 虚拟机栈中引用的对象 方法区中类静态属性引用的对

Java虚拟机垃圾收集器与内存分配策略

Java虚拟机垃圾收集器与内存分配策略 概述 那些内存需要回收,什么时候回收,如何回收是GC需要完成的3件事情. 程序计数器,虚拟机栈与本地方法栈这三个区域都是线程私有的,内存的分配与回收都具有确定性,内存随着方法结束或者线程结束就回收了. java堆与方法区在运行期才知道创建那些对象,这部分内存分配是动态的,本章笔记中分配与回收的内存指的就是:java堆与方法区. 判断对象已经死了 引用计数算法:给对象添加一个引用计数器,每当有一个地方引用它,计数器+1;引用失败,计数器-1.计数器为0则改判

第三章 垃圾收集器和内存分配策略

第三章 垃圾收集器和内存分配策略 对象已死吗 引用计算方法 可达性分析算法 通过一些列的GC roots 对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径成为引用链,当一个对象到GC roots 没有任何引用链的则证明对象不可用的 虚拟机栈中的引用的对象 方法区中类静态属性引用的对象 方法去区中常量引用的对象 本地方法栈中JNI引用的对象 生存还是死亡 一次筛选,筛选是否有必要执行 finalize()方法 没有覆盖或者finalize()已经被调用过  视为没必要执行 放入一个F-Qu

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

垃圾收集算法简介 1.标记-清除算法       标记-清除算法主要分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一进行回收.对象的标记过程在垃圾收集器与内存分配策略(一)中已经介绍过. 存在的问题:一是效率问题,标记和清除的效率都不高:二是空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时无法找到足够的内存而不得不提前触发另一次垃圾收集动作. 2.复制算法       复制算法:它将内存按照容量划分为大小

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

垃圾收集器与内存分配策略(四)--垃圾收集器 收集算法是内存回收的方法论,垃圾收集器则是内存回收的具体实现. 垃圾收集器介绍 在垃圾收集器的层面上对并行与并发的解释: 并行(Parallel):指多条垃圾收集线程并行工作,但此时用户现场仍处于等待状态. 并发(Concurrent):指用户线程与垃圾收集线程同时执行(但并不一定是并行的,可能会交替执行),用户程序仍在继续执行,而垃圾收集程序运行于另一个CPU上. 对于不同的厂商,不同的版本的虚拟机都可能有很大的差别.此处讨论的是jdk1.7之后的

垃圾收集器与内存分配策略(三)之HotSpot的算法实现

垃圾收集器与内存分配策略(三)--HotSpot的算法实现 Java JVM 垃圾回收 在HotSpot虚拟机上实现这些算法时,必须对算法的执行效率有着严格的考量,才能保证虚拟机高效地运行. 1. 枚举根节点 采用可达性分析从GC Roots节点中找引用链为例 存在的缺点: 1.在前面找出还存活对象时,采用可达性分析从GC Roots节点中找引用链时,可作为GC Roots的节点主要在全局性的引用(方法区的常量或类静态属性引用)与执行上下文(虚拟机栈栈帧中的本地变量表或本地方法栈中的Native

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

垃圾收集器与内存分配策略(六)--内存分配与回收策略 对象的内存分配,一般来说就是在堆上的分配(但也可能经过JIT编译后被拆散为标量类型并间接地栈上分配),对象分配的细节取决于当前使用的是哪一种垃圾收集器组合,还有虚拟机中与内存相关的参数设置. 区分Minor GC与 Full GC: 新生代GC(Minor GC):指发生在新生代的的垃圾收集动作,因为Java对象大多具有朝生夕灭的特性,所以Minor GC非常频繁,一般回收速度也比较快. 老年代GC(Full GC / Major GC):老

垃圾收集器与内存分配策略(五)之垃圾日志与常见参数

垃圾收集器与内存分配策略(五)--垃圾日志与常见参数 理解GC日志 每个收集器的日志格式都可以不一样,但各个每个收集器的日志都维持一定的共性.如下面二段日志: 33.125: [GC [DefNew: 3324K->152K(3712K), 0.0025925 secs] 3324K->152K(11904K), 0.0031680 secs] 100.667: [Full GC [Tenured: 0K->210K(10240K), 0.0149142 secs] 4603K->