JVM_7_垃圾搜集器

来源:互联网 发布:淘宝购物返现app排行榜 编辑:程序博客网 时间:2024/06/12 01:07

垃圾搜集器


参考资料:

《 Java虚拟机垃圾回收(三) 7种垃圾收集器:主要特点 应用场景 设置参数 基本运行原理》


我们这里看下Hotspot虚拟机的七种垃圾收集器:

Serial、ParNew、Parallel Scavenge、Serial Old、Parallel Old、CMS、G1


垃圾收集器是垃圾收集算法(标记-清除算法、复制算法、标记-整理算法)的具体实现,不同厂商有不同的实现,我们主要看Hotspot。



Hotspot垃圾收集器

JDK7/8之后,Hotpost虚拟机内所有的垃圾收集器及其组合(连线),下图:


他们所处区域,表明其所属新生代收集器还是老年代收集器:

新生代垃圾收集器:Serial、ParNew、Parallel Scavenge

老年代垃圾收集器:Serial Old、Parallel Old、CMS

整堆收集器G1


两个收集器之间有连线,表明他们可以搭配使用:

Serial/Serial Old、Serial/CMS、

ParNew/Serial Old、ParNew/CMS、

Parallel Scavenge/Serial Old、Parallel Scavenge/Parallel Old、

G1



并发垃圾收集器和并行垃圾收集器的区别

1. 并行(Parallel)

多条垃圾收集线程并行工作,但此时用户线程处于等待状态

如ParNew、Pallel Scavenge、Parallel Old


2. 并发(Concurrent)

用户线程与垃圾收集线程同时执行(不一定是并行的,可能会是交替执行);

用户程序线程继续运行,而垃圾收集线程运行于另一个CPU上

如CMS



Minor GC 和 Full GC的区别

1. Minor GC(新生代GC)

又称新生代GC,指发生在新生代区域的垃圾收集动作;

因为Java对象大多是朝生夕灭的,所以Minor GC非常频繁,一般回收速度也比较快。


2. Major GC 、Full GC(年老代GC)

又称老年代GC ,指发生在老年代区域的垃圾收集动作;

出现Full GC(年老代GC)经常会伴随至少一次的Minor GC(不是绝对的,Parallel Sacvenge收集器可以设置策略)

Full GC 比 Minor GC 要慢10倍以上。


----------------------------------------------------------------------------------------------

下面开始,详细的垃圾收集器介绍

----------------------------------------------------------------------------------------------




Serial收集器

serial收集器是最基本、历史最悠久的收集器。它是一个单线程的收集器

它"单线程"的意义并不仅仅说明它只会使用一个CPU或一条收集线程去完成垃圾收集工作,更重要的是它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束

"Stop the World"这项工作实际上是由虚拟机在后台自动发起和自动完成的,在用户不可见的情况下把用户正常工作的线程全部停掉,用户体验非常差

我们看下Serial收集器的运行示意图:(书中原图)

到目前为止,Serial收集器依然是虚拟机运行在Client模式下的默认新生代收集器。

Serial收集器的优点:简单高效

------------------------------------------------------------------------------------------------------------------------

Serial收集器在JDK1.3之前是Hotspot新生代收集的唯一选择

1. 特点

针对新生代;

采用复制算法;

单线程收集;

它进行垃圾收集时,必须暂停所有工作线程,直到收集完成,即"Stop the World"

运行示意图:

2. 应用场景

是Hotspot虚拟机在Client模式下 新生代收集器;

对于限定单个CPU的环境来说,Serial收集器没有线程交互(切换)开销,可以获得最高的单线程收集效率;


3. 设置参数

"-XX:+UseSerialGC" : 该参数用来显示的添加串行垃圾收集器。


4. Stop the World 说明

JVM在后台自动发起和自动完成的,在用户不可见的情况下,把用户正常的工作线程全部停掉,即GC停顿;

GC停顿带来的用户体验非常差;

从Serial收集器-->Parallel收集器-->CMS-->G1收集器,用户线程停顿的时间在不断缩短,但仍然没法完全消除。





ParNew收集器

ParNew收集器其实就是Serial收集器的多线程版本,除了使用多线程进行垃圾收集之外,其余行为都与Serial收集器一样。

我们看下ParNew收集器工作过程示意图:


ParNew收集器除了多线程收集之外,其他与Serial收集器相比之下,并没有太多创新之处

不过在Server模式下,它是虚拟机首选的新生代收集器,因为它可以与CMS收集器配合使用。


ParNew收集器在单CPU的环境中绝对不会有比Serial收集器更好的效果,甚至由于线程交互的开销,不能百分百保证可以超过Serial收集器。

当然随着可以使用的CPU的数量增加,它对于GC时系统资源的有效利用还是很有好处的。


它默认开启的收集线程数量与CPU的数量相同。

在CPU非常多的环境下,可以使用"-XX:ParallelGCThreads"参数来限制垃圾收集的线程数量。

------------------------------------------------------------------------------------------------------------------------

ParNew垃圾收集器是Serial收集器的多线程版本


1. 特点

除了多线程之外,其余的行为、特点与Serial收集器一样。

工作过程示意图:


2. 应用场景

在Server模式下,ParNew收集器是一个非常重要的收集器,因为除Serial外,目前只有它能与CMS收集器配合工作。

在但CPU环境中,不会比Serial收集器有更好的效果,因为存在线程交互开销。


3. 参数设置

"-XX:+UseConcMarkSweepGC":指定使用CMS后,会默认使用ParNew作为新生代收集器。

"-XX:+UseParNewGC":强制指定使用ParNew收集器

"-XX:ParallelGCThreads":指定垃圾收集的线程数量,ParNew默认开启的收集线程与CPU的数量相同。





Parallel Scavenge收集器

Parallel Scavenge收集器是一个新生代收集器,它也是使用复制算法的收集器,又是并行的多线程收集器。


Parallel Scavenge收集器和ParNew的关注点不太一样,其他的收集器关注的都是尽可能的缩短垃圾收集时用户线程停顿的时间

而Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量


所谓吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值:

吞吐量 = 运行用户代码时间 / (运行用户代码时间+垃圾收集时间)

虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。


停顿时间越短就越适合于用户交互的程序,良好的响应速度能提升用户体验

而高吞吐量则可以高效率的利用CPU时间,尽快完成程序的运算任务,适合在后台运算而不需要太多交互的任务。


"-XX:MaxGCPauseMillis"

控制最大垃圾收集停顿时间;

参数是大于0的毫秒数,收集器将尽可能保证内存回收不超过设定的值。

GC停顿时间缩短是以牺牲吞吐量量和新生代空间来换取的。

以前10秒收集一次,每次停顿100毫秒,现在变成5秒收集一次,每次停顿70毫秒;停顿时间的确在下降,但吞吐量也下降了。


"-XX:GCTimeRatio"

直接设置吞吐量大小;

参数的值应当是一个大于0且小于100的整数,也就是垃圾收集时间占总时间的比率,相当于是吞吐量的倒数。

如果把此参数设置为19,那允许的最大GC时间就占总时间的5%(即 1/(1+19)),默认值是99,就是允许最大1%(即 1/(1+99))的垃圾收集时间


由于与吞吐量关系密切,Parallel Scavenge收集器也被称为"吞吐量优先"收集器


"-XX:+UseAdaptiveSizePolicy"

这是一个参数开关,当这个参数打开之后,就不需要手工指定新生代大小等细节参数了,虚拟机会根据当前系统运行状态收集性能监控信息。

动态调整这些参数,这种调节方式被称为"GC自适应的调节策略"。


自适应策略也是Parallel Scavenge收集器与ParNew收集器一个重要区别。

------------------------------------------------------------------------------------------------------------------------

Parallel Scavenge垃圾收集器因为与吞吐量关系密切,也称为吞吐量收集器


1. 特点

新生代收集器、采用复制算法、多线程收集 ---- 与ParNew收集器相同

主要特点:

它关注吞吐量,目标是达到一个可控制的吞吐量


2. 应用场景

高吞吐量为目标,即减少垃圾收集时间,让用户代码获得更长的运行时间。

当应用程序运行在多个CPU上,对暂停时间没有特别高的要求时,即程序主要在后台运行计算,而不需要与用户进行太多的交互


3. 设置参数

          a. -XX:MaxGCPauseMillis

                    控制最大垃圾收集时间,大于0的毫秒数。

                    MaxGCPauseMillis设置的稍小,停顿时间可能会缩短,但也可能会使得吞吐量下降,因为可能导致垃圾收集发生的更频繁。


          b. -XX:GCTimeRatio

                    设置垃圾收集时间占总时间的比率,0 < n < 100的整数。

                    GCTimeRatio相当于设置吞吐量大小。

                    垃圾收集执行时间占应用程序执行时间的比例计算方法:

                              1 / (1+n)

                              例如,选项-XX:GCTimeRatio=19,设置了垃圾收集时间占总时间的5%,1 / (1+19)

                    它的默认值为1%,1 / (1+99),即n = 99;

                    垃圾收集所花费的时间是年轻代和年老代收集的总时间;


          c. -XX:+UseAdaptiveSizePolicy

                    开启这个参数之后,就不用手工指定一些细节参数,JVM会动态调整,GC自适应的调节策略。


4. 吞吐量与收集器关注点说明

          a. 吞吐量

                    CPU用于运行用户代码的时间 与 CPU总消耗时间的比值,即:

                    吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间);

                    高吞吐量即减少垃圾收集时间,让用户代码获得更长时间的运行时间。


          b. 垃圾收集器期望的目标

                    a. 停顿时间

                              停顿时间越短就越适合于用户交互的程序,良好的响应速度能提升用户体验

                    b. 吞吐量

                              高吞吐量则可以高效率利用CPU时间,尽快完成运算任务。适合在后台计算而不需要太多交互的任务

                    c. 覆盖区

                              在达到前面两点的情况下,尽量减少堆的内存空间,可以获得更好的空间局部性





Serial Old收集器

serial Old是Serial收集器的年老代版本,同样是一个单线程收集器,使用"标记-整理"算法


这个收集器的主要意义也是在于给Client模式下的虚拟机使用的。


在Server模式下,它还有两大用途:

a. 在JDK1.5之前的版本与Parallel Scavenge收集器搭配使用。

b. 作为CMS收集器的后备方案。(后面再说)


执行过程如下图:(书中原图)


------------------------------------------------------------------------------------------------------------------------






Parallel Old收集器


Parallel Old收集器是Parallel Scavenge收集器的老年代版本;


1. 特点

针对年老代;

采用"标记-整理"算法;

多线程收集;

运行过程如下图:

2. 应用场景

JDK1.6之后才出现,用来代替年老代的Serial Old收集器。

在Server模式、多CPU、这种注重吞吐量以及CPU资源敏感的场景,就有了Parallel Scavenge + Parallel Old收集器这样给力的组合。


3. 设置参数

"-XX:+UseParallelOldGC":指定使用Parallel Old收集器。





CMS收集器


CMS并发标记清理收集器也被称为并发低停顿收集器低延迟垃圾收集器


CMS收集器是一种以获取最短回收停顿时间为目标的收集器。


1. 特点

针对年老代;

基于"标记-清除"算法;

以获取最短回收停顿时间为目标;

并发收集、低停顿;

需要更多的内存;

JDK1.5之后推出的第一款真正意义上的并发收集器;

第一次实现了让垃圾收集器与用户线程同时工作;


2. 应用场景

与用户交互较多的场景;

希望系统停顿时间最短,注重服务的相应速度;提高用户体验。(常见web、b/s系统的服务上的应用)


3. 设置参数

"-XX:+UseConcMarkSweepGC": 指定使用CMS收集器


4. CMS收集器运作过程

CMS收集器的运行过程比较复杂,分文以下四步:

          a. 初始标记

                    仅标记一下GC Roots能直接关联到的对象;

                    速度很快,但需要"Stop the World"

          b. 并发标记

                    进行GC Roots Tracing的过程;

                    应用程序也在运行;

                    并不能保证可以标记出所有的存活对象;

          c. 重新标记

                    为了修正并发标记期间因用户程序继续运行而导致标记变动的那一部分对象的标记记录。

                    需要"Stop the World",且停顿时间比初始标记稍长,但远比并发标记短。

                    采用多线程并行执行来提升效率。


          d. 并发清除

                    回收所有的垃圾对象。

                    整个过程中耗时最长的并发标记和并发清除都可以与用户线程一起工作;

                    所以总体上说,CMS收集器的内存回收过程与用户线程一起并发执行;


CMS收集器运行过程示意图:


5. CMS收集器3个明显的缺点

a. 对CPU资源非常敏感

          并发收集虽然不会暂停用户线程,但因为占用一部分CPU资源,还是会导致应用程序变慢,总吞吐量降低。

          CMS默认的收集线程数量是 = (CPU数量 + 3)/4;

          当CPU数量多于4个,收集线程占用的CPU资源多于25%,对用户程序影响可能较大;

          不足4个时,影响更大,可能无法接受。


          注:

                    针对这种情况,曾出现了"增量式并发收集器"。

                    类似使用抢占式来模拟多任务机制的思想,让收集线程和用户线程交替运行,减少收集线程运行时间。

                    不过效果不理想,JDK1.6之后不提倡使用。


b. 无法处理浮动垃圾,可能出现"Concurrent Mode Failure"失败

          1. 浮动垃圾

                    在并发清除时,用户线程新产生的垃圾,称为浮动垃圾;

                    这使得并发清除时需要预留一定的内存空间,不能像其他收集器一样,在年老代几乎填满再进行收集。

                    也可以认为CMS收集器所需要的空间比其他垃圾收集器大。

                    "-XX:CMSInitiatingOccupancyFraction":设置CMS预留内存空间;

                    JDK1.5默认设置下,CMS收集器当年老带使用了68%的空间之后就会被激活。

                    JDK1.6大约 年老代使用了92%的空间之后,CMS收集器就会被激活。


          2. "Concurrent Mode Failure"失败

                    如果CMS预留内存空间无法满足程序需要,就会出现一次"Concurrent Mode Failure"失败。

                    这时JVM启动后备预案:临时启动Serial Old收集器,而导致另一次Full GC的产生。

                    这样做的代价是很大的,所以CMSInitiatingOccupancyFraction不能设置的太大。


c. 产生大量内存碎片

          由于CMS收集器基于"标记-清除"算法,清除后不能进行压缩操作。

          之前在讲"标记-清除"算法的时候,曾介绍过:

          产生大量不连续的内存碎片会导致分配大内存对象时,无法找到足够的连续内存,从而需要提前触发另一次Full GC动作。

          解决方法:

                    1. "-XX:+UseCMSCompactAtFullCollection"

                              使得CMS出现上面这种情况时,不进行Full GC,而是开启内存碎片的合并并整理。

                              但是合并整理过程无法并发,停顿时间会变长。

                              默认开启(但不会进行,结合下面的CMSFullGCBeforeCompaction)


                    2. "-XX:+CMSFullGCBeforeCompaction"

                              设置执行多少次不压缩的Full GC之后,来一次压缩整理。

                              为了减少合并整理过程的停顿时间。

                              默认为0,也就是说每次执行Full GC,不会进行压缩整理。

                              由于空间不在连续,CMS需要使用可用"空闲列表"内存分配方式,这比简单实用的"碰撞指针"分配内存消耗大。

                              总的来看,与Parallel Old垃圾收集器相比,CMS减少了执行年老代垃圾收集时应用暂停的时间,

                              但却增加了新生代垃圾收集时应用暂停的时间、降低了吞吐量,而且需要占用更大的堆空间。






G1收集器


G1是一款面向服务端应用的垃圾收集器。

1. 特点

a. 并行与并发

能充分利用多CPU、多核环境下的硬件优势;

可以并行来缩短"Stop the World"停顿时间;

也可以并发让垃圾收集与用户程序同时进行


b. 分代收集,收集范围包括新生代和年老代

能独立管理整个GC堆(新生代、年老代),而不需要与其他收集器搭配。

能够采用不同方式处理不同时期的对象。


虽然保留分代概念,但Java堆的内存布局有很大差别。

将这个堆划分为多个大小相等的独立区域(Region);

新生代和年老代不再是物理隔离,它们都是一部分Region(不需要连续)的集合。


以下是书中原文,关于Region的解释:

在G1之前的其他收集器进行收集的范围都是整个新生代或者年老代,而G1不再是这样。

使用G1收集器时,Java堆的内存布局就与其他收集器有很大差别,它将整个Java堆划分为多个大小相等的独立区域(Region),

虽然还保留有新生代和年老代的概念,但新生代和年老代不再是物理隔离得到了,它们都是一部分Region(不需要连续)的集合。


c. 结合多种垃圾收集算法、空间整合,不产生碎片

从整体来看,是基于标记-整理算法

从局部(两个Region)来看,是基于复制算法

两种算法都不会产生内存碎片,收集后能提供规整的可用内存,有利于长时间运行。


d. 可预测的停顿:低停顿的同时实现高吞吐量

G1除了追求低停顿外,还能建立可预测的停顿时间模型;

可以明确指定M毫秒时间片内,垃圾收集消耗的时间不超过N毫秒


2. 应用场景

面向服务端应用,针对具有大内存、多处理器的机器

最主要是为 需要 低GC延迟,并具有大堆的应用程序提供解决方案;

如:在堆大小约6GB或更大时,可预测的暂停时间可以低于0.5秒。

用来替换掉JDK1.5中的CMS收集器


在下面的情况中,使用G1可能比CMS好:

a. 超过50%的Java堆被活动数据占用

b. 对象分配频率或年老代提升频率变化很大

c. GC停顿时间过长(长于0.5 至 1秒)


那么是否应要采用G1呢?也不一定:

如果现在采用的收集器没有出现问题,不用着急去选择G1,如果应用程序追求低停顿,可以尝试选择G1;

是否代替CMS需要实际场景测试才知道


3. 设置参数

"-XX:UseG1GC":指定使用G1收集器。

"-XX:InitiatingHeapOccupancyPercent":当整个Java堆的占用率达到参数值时,开始并发标记阶段,默认为45。

"-XX:MaxGCPauseMillis":为G1收集器设置暂停时间目标,默认值为200毫秒

"-XX:G1HeapRegionSize":设置每个Region大小,范围1MB到32MB;在最小Java堆时可以拥有大约2048个Region


4. 为什么G1垃圾收集器可以实现"可预测"的停顿

G1垃圾收集器可以建立可预测得到停顿时间模式,是因为:

可以有计划的避免 在Java堆进行全区域的垃圾收集。

G1跟踪各个Region获得其收集价值大小,在后台维护一个优先列表。

每次根据允许的收集时间,优先回收价值最大的Region。


这样子,就保证了在有限的时间内可以获取尽可能高的收集效率。


5. G1收集器运作过程

G1垃圾收集器的运作过程与CMS比较相似。

a. 初始标记

          仅标记一下GC Roots能直接关联到的对象。

          且修改TAMS(Next Top at Mark Sart),让下一阶段并发运行时,用户程序能在正确可用的Region中创建新对象。

          需要"Stop The World",但速度很快。


b. 并发标记

          进行GC Roots Tracing的过程。

          刚才产生的集合中标记出存活对象。

          耗时较长,但应用程序也在运行。

          并不能保证可以标记出所有的存活对象。


c. 最终标记

          为了修正并发标记期间,因用户程序继续运作,而导致标记变动的那一部分对象的标记记录。

          需要"Stop The World",且停顿时间比初始标记稍长,但远比并发标记短。

          采用多线程并行执行来提高效率。


d. 筛选回收

          首先排序各个Region的回收价值和成本;

          然后根据用户期望的GC停顿时间来制定回收计划;

          最后按计划回收一些价值高的Region中的垃圾对象;

          回收时采用"复制"算法,从一个或多个Region复制存活对象到堆上的另一个空的Region,并且在此过程中压缩和释放内存;

          可以并发运行,降低停顿时间,并增加吞吐量。


G1收集器运行过程如下图: