JVM垃圾收集器整理

时间:2022-10-17 18:21:08

概述

垃圾收集器是jvm实现内存回收的具体实现。本次分享要介绍的7种垃圾收集器的作用区域及其之间的关系如下图:
JVM垃圾收集器整理

注:

  • 如果2个垃圾收集器之间有连线,表示可以搭配使用
  • 垃圾收集器并没有最好的,只有针对不同应用场景最合适的

(1)Serial收集器

算法 内存区域 执行方式
复制算法 新生代 单线程、串行

过程

先暂停全部用户线程(Stop The World),然后开启一条GC线程使用复制算法对垃圾进行回收。直到收集结束被暂停的线程才能继续执行 JVM垃圾收集器整理

特点

  • 简单而高线,对单CPU环境没有线程交互的开销。
  • 由于单线程运行,且整个GC阶段都要暂停用户线程,因此会造成应用程序停顿。
  • client模式下的默认垃圾收集器

使用场景

平时的开发与调试程序使用,以及桌面应用交互程序。

(2)Serial Old收集器

算法 内存区域 执行方式
标记/整理算法 老年代 单线程、串行

执行过程、特点、使用场景与Serial收集器类似

(3)ParNew收集器

算法 内存区域 执行方式
复制算法 新生代 多线程、并行

过程

开启多个GC线程使用复制算法并行进行垃圾回收 JVM垃圾收集器整理

特点

  • 使用多个线程去并行执行垃圾回收,在发挥多处理器的优势
  • 在单处理器上的性能会低于Serial收集器(线程交互开销)
  • 执行垃圾回收也需要暂停其他用户线程

使用场景

在中到大型的堆上,且系统处理器至少多于一个的情况。

(4)Parallel Scavenge收集器

算法 内存区域 执行方式
复制算法 新生代 并行

过程

ParNew收集器基本类似,只不过优先满足最大停顿时间的目标,次之是吞吐量,最后才是新生代区域的最小值。

吞吐量=运行用户代码的时间/CPU总消耗时间

特点

  • 更精确的控制GC停顿时间以及吞吐量
  • 控制最大的停顿时间(使用-XX:MaxGCPauseMillis=),以及控制吞吐量(使用-XX:GCTimeRatio=)

使用场景

适用于与用户交互的程序,良好的响应速度能提升用户体验,高吞吐量则可以高效的利用cpu。主要适合在后台运算而不需要太多交互的任务

Parallel Old收集器

算法 内存区域 执行方式
标记/整理算法 老年代 并行

特点

除了serial old以外唯一一个可以与parallel scavenge搭配工作的年老代搜集器

CMS收集器

算法 内存区域 执行方式
标记/清除算法 老年代 多线程、并行

过程

  • 初始标记(CMS initial mark):需要用户线程停顿。标记一下GC Roots能直接关联到的对象
  • 并发标记(CMS concurrent mark):以初始标记的对象为root进行并发标记。
  • 重新标记(CMS remark):需要用户线程停顿,是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录。
  • 并发清除(CMS concurrent sweap):清除标记的对象 JVM垃圾收集器整理

特点

1.会有2次Stop The World

1.初始标记阶段,时间很短,只会标记直接与GC Root相连的对象 2.重新标记阶段,时间比初始化标记稍长

2.浮动垃圾

产生原因

在进行标记阶段,标记的对象不再从GC Root可达,因此在这次GC过程中该对象内存空间不会被回收,因此造成了浮动垃圾。

影响

这些浮动垃圾会占用一些内存,但下次GC会被回收。

3.三色标记法

产生原因

并发标记阶段,GC线程没有标记(用户线程之前没在用),在标记过程中,用户线程又重新引用了该对象。如果该对象依然被回收,那么程序就直接报错了。

标记过程

下面举个例子说明,设计A是被GC Root相关联的对象,假设现在正在搜索A相关联的对象,这个时候A被标记为灰色,搜索到A引用了B,这个时候B也会被标记为灰色,如下图

JVM垃圾收集器整理
当A搜索完以后,A被标记为绿色,这个时候直接与A相连的B和C已被标记为灰色,但D没有,因为D没有直接与A相连,如下图
JVM垃圾收集器整理
然后继续搜索B直接引用的对象,假设这个过程中,C不再引用D,如下图
JVM垃圾收集器整理
如果在搜索C相关的引用后,D依然处于不可达的状态,假设这个时候已经标记完成的对象B引用了D,如下图
JVM垃圾收集器整理 如果D被回收,那么在后面的程序肯定会有问题

解决办法

此时的解决办法就是有一个叫做写入屏障(注意在G1收集器中会比较)的东西。就是说,如果B已经被标记了(已经是绿色的了),那么用户线程改动 B->D的时候,会把D变成灰色,这样,以后就可以搜索D了。 JVM垃圾收集器整理

4.Concurrent Mode Failure

由于其他垃圾回收器都是 "stop the world",那么内存不够了就执行 GC。但是 CMS 垃圾回收器是可以和用户线程一起并发的。假设在执行GC过程中内存不够了怎么办?这个时候就会有预备方案,Serial Old 垃圾回收器会替代 CMS,进行 "stop the world"垃圾收集。

使用-XX:CMSInitiatingOccupancyFraction的值来设定触发GC的内存百分比

5.空间碎片

CMS是一款基于“标记-清除”算法实现的收集器,收集结束时会有大量的空间碎片产生。当无法找到足够大的连续空间来分配当前对象,不得不提前触发一次full Gc。

-XX:+UseCMSCompactAtFullCollection

默认是打开的,用户在CMS收集器顶不要进行Full GC时开启内存碎片的合并整理过程,内存整理的过程是无法并发的,空间碎片的问题没有了,但停顿的时间会变长

-XX:CMSFullGCBeforeCompaction

设置多少次不压缩Full GC后,跟着来一次带压缩的碎片整理,默认为0。

6.吞吐量影响

CMS收集器对CPU资源非常敏感.CMS默认启动的回收线程数是(CPU数量+3)/4,在并发阶段,它虽然不会导致用户线程停顿,但是会占用一部分线程而导致应用程序变慢,总吞吐量对降低。

使用场景

重视服务的响应速度,希望系统的停顿时间最短,以带来较好的用户体验,例如互联网站或B/S系统的服务端上,

G1收集器

CMS垃圾收集器虽然减少了暂停应用程序的运行时间,但是它还是存在着内存碎片问题。于是,为了去除内存碎片问题,同时又保留CMS垃圾收集器低暂停时间的优点,JAVA7发布了一个新的垃圾收集器 - G1垃圾收集器。

内存组织方式变更

Eden,Survivor和Tenured等内存区域不再是连续的了,而是变成了一个个大小一样的region - 每个region从1M到32M不等。一个region有可能属于Eden,Survivor或者Tenured内存区域。 JVM垃圾收集器整理 在G1中,还有一种特殊的区域,叫Humongous区域。 如果一个对象占用的空间超过了分区容量50%以上,G1收集器就认为这是一个巨型对象。G1划分了一个Humongous区,它用来专门存放巨型对象。如果一个H区装不下一个巨型对象,那么G1会寻找连续的H分区来存储。为了能找到连续的H区,有时候不得不启动Full GC。

新生代收集

在G1垃圾收集器中CMS垃圾收集器差不多,回收Eden region和Survivor region上的非可达对象,同时升级存活的可达对象到对应的Survivor region或Tenured region上。 JVM垃圾收集器整理

老年代收集

对于年老代上的垃圾收集,G1垃圾收集器也分为4个阶段,基本跟CMS垃圾收集器一样,但略有不同:

    • 初始化标记:过程和CMS基本类似,不同的是该阶段在触发YGC的时候执行(有什么好处?)
    • 并发标记阶段:和CMS基本类似,不同的是在该阶段,G1会计算每个 region的对象存活率,方便后面的clean up阶段使用。
    • 重新标记:和CMS基本类似,但采取算法不一样,G1采用一种叫做SATB(snapshot-at-the-begining)的算法能够在重新阶段更快的标记可达对象。(CMS是使用写入屏障)
    • Clean up/Copy阶段 - 在G1中,没有CMS中对应的并发清楚阶段。相反 它有一个Clean up/Copy阶段,在这个阶段中,G1会挑选出那些对象存活率低的region进行回收,这个阶段也是和minor gc一同发生的,如下图所示JVM垃圾收集器整理