垃圾收集器

垃圾收集算法

标记清除

算法分为“标记”和“清除”两个阶段:首先标记出所有需要回 收的对象,在标记完成后,统一回收掉所有被标记的对象,也可以反过来,标记存活的对象,统一回 收所有未被标记的对象
在这里插入图片描述
特点:位置不连续 产生碎片 效率偏低(两遍扫描)

标记-复制算法

它将可用 内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着 的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉

在这里插入图片描述
特点:没有碎片,浪费空间

标记-整理算法

标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可 回收对象进行清理,而是让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存
在这里插入图片描述
特点:没有碎片,效率偏低(两遍扫描,指针需要调整)

经典垃圾收集器

Serial收集器

单线程垃圾收集器:在进行垃圾收集时必须停止工作的线程
适用于: 收集几十兆甚至一两百兆的新生代
在这里插入图片描述

ParNew收集器

ParNew是一个多线程的 Serial ,可以配合CMS使用,作为新生代的收集器
在这里插入图片描述

Parallel Scavenge收集器

Parallel Scavenge垃圾收集器是基于标记复制算法实现的新生代,主要目标是达到一个可控制的吞吐量
在这里插入图片描述

Serial Old收集器

Serial Old是Serial收集器的老年代版本,它同样是一个单线程收集器,使用标记-整理算法。他有两种用 途:一种是在JDK 5以及之前的版本中与Parallel Scavenge收集器搭配使用[1],另外一种就是作为CMS 收集器发生失败时的后备预案,在并发收集发生Concurrent Mode Failure时使用。
在这里插入图片描述

Parallel Old收集器

Parallel Old是Parallel Scavenge收集器的老年代版本,支持多线程并发收集,基于标记-整理算法实 现。
在这里插入图片描述

CMS收集器

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的,是基于标记-清除算法实现的收集器。目前很大一部分的Java应用集中在互联网网站或者基于浏览器的B/S系统的服务端上。
整个过程分为四个步骤:

  1. 初始标记(CMS initial mark)
  2. 并发标记(CMS concurrent mark)
  3. 重新标记(CMS remark)
  4. 并发清除(CMS concurrent sweep)
    CMS是一款优秀的收集器,它最主要的优点在名字上已经体现出来:并发收集、低停顿,一些官 方公开文档里面也称之为“并发低停顿收集器”
    缺点:
    1、CMS默认启动的回收线程数是(处理器核心数量 +3)/4,如果处理器核心数在四个或以上,并发回收时垃圾收集线程只占用不超过25%的 处理器运算资源,并且会随着处理器核心数量的增加而下降/2、2、CMS收集器无法处理“浮动垃圾”
    3、CMS是一款基于“标记-清除”算法实现的收集器,会产生空间碎片

在这里插入图片描述

Garbage First收集器(G1)

jdk8 默认收集器 Parallel Scavenge加Parallel Old组合
jdk9的默认垃圾收集器

  • G1开创的基于Region的堆内存布局,把连续的Java堆划分为多个大小相等的独立区域(Region),每一个Region都可以根据需要,扮演新生代的Eden空间、Survivor空间,或者老年代空间。
  • Region中还有一类特殊的Humongous区域,专门用来存储大对象。G1认为只要大小超过了一个 Region容量一半的对象即可判定为大对象。每个Region的大小可以通过参数-XX:G1HeapRegionSize设 定,取值范围为1MB~32MB,且应为2的N次幂。而对于那些超过了整个Region容量的超级大对象, 将会被存放在N个连续的Humongous Region之中,G1的大多数行为都把Humongous Region作为老年代 的一部分来进行看待
  • 根据用户设定允许的收集停顿时间,(使用参数-XX:MaxGCPauseMillis指定,默 认值是200毫秒),优先处理回收价值收益最大的那些Region
    在这里插入图片描述
  • ·初始标记(Initial Marking):仅仅只是标记一下GC Roots能直接关联到的对象,并且修改TAMS
    指针的值,让下一阶段用户线程并发运行时,能正确地在可用的Region中分配新对象。这个阶段需要停顿线程,但耗时很短,而且是借用进行Minor GC的时候同步完成的,所以G1收集器在这个阶段实际 并没有额外的停顿。
  • ·并发标记(Concurrent Marking):从GC Root开始对堆中对象进行可达性分析,递归扫描整个堆
    里的对象图,找出要回收的对象,这阶段耗时较长,但可与用户程序并发执行。当对象图扫描完成后,还要重新处理SATB记录下的在并发时有引用变动的对象。
  • ·最终标记(Final Marking):对用户线程做另一个短暂的暂停,用于处理并发阶段结束后仍遗留 下来的最后那少量的SATB记录。
  • ·筛选回收(Live Data Counting and Evacuation):负责更新Region的统计数据,对各个Region的回收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个Region构成回收集,然后把决定回收的那一部分Region的存活对象复制到空的Region中,再清理掉整个旧Region的全部空间。这里的操作涉及存活对象的移动,是必须暂停用户线程,由多条收集器线程并行 完成的。
    在这里插入图片描述

Shenandoah收集器

Shenandoah作为第一款不由Oracle(包括以前的Sun)公 司的虚拟机团队所领导开发的HotSpot垃圾收集器,是由RedHat公司独立发展的新型收集器项目,在2014年RedHat把Shenandoah贡献 给了OpenJDK,并推动它成为OpenJDK 12的正式特性之一。这个项目的目标 是实现一种能在任何堆内存大小下都可以把垃圾收集的停顿时间限制在十毫秒以内的垃圾收集器,该 目标意味着相比CMS和G1,Shenandoah不仅要进行并发的垃圾标记,还要并发地进行对象清理后的整 理动作。
在这里插入图片描述

  • ·初始标记(Initial Marking):与G1一样,首先标记与GC Roots直接关联的对象,这个阶段仍 是“Stop The World”的,但停顿时间与堆大小无关,只与GC Roots的数量相关。
  • ·并发标记(Concurrent Marking):与G1一样,遍历对象图,标记出全部可达的对象,这个阶段
    是与用户线程一起并发的,时间长短取决于堆中存活对象的数量以及对象图的结构复杂程度。
  • ·最终标记(Final Marking):与G1一样,处理剩余的SATB扫描,并在这个阶段统计出回收价值
    最高的Region,将这些Region构成一组回收集(Collection Set)。最终标记阶段也会有一小段短暂的停 顿。
  • ·并发清理(Concurrent Cleanup):这个阶段用于清理那些整个区域内连一个存活对象都没有找到的Region(这类Region被称为Immediate Garbage Region)。
  • ·并发回收(Concurrent Evacuation):并发回收阶段是Shenandoah与之前HotSpot中其他收集器的核心差异。在这个阶段,Shenandoah要把回收集里面的存活对象先复制一份到其他未被使用的Region之中。复制对象这件事情如果将用户线程冻结起来再做那是相当简单的,但如果两者必须要同时并发进行的话,就变得复杂起来了。其困难点是在移动对象的同时,用户线程仍然可能不停对被移动的对象进行读写访问,移动对象是一次性的行为,但移动之后整个内存中所有指向该对象的引用都还是旧对象的地址,这是很难一瞬间全部改变过来的。对于并发回收阶段遇到的这些困难,Shenandoah将会通 过读屏障和被称为“Brooks Pointers”的转发指针来解决(讲解完Shenandoah整个工作过程之后笔者还要 再回头介绍它)。并发回收阶段运行的时间长短取决于回收集的大小。
  • ·初始引用更新(Initial Update Reference):并发回收阶段复制对象结束后,还需要把堆中所有指
    向旧对象的引用修正到复制后的新地址,这个操作称为引用更新。引用更新的初始化阶段实际上并未 做什么具体的处理,设立这个阶段只是为了建立一个线程集合点,确保所有并发回收阶段中进行的收集器线程都已完成分配给它们的对象移动任务而已。初始引用更新时间很短,会产生一个非常短暂的 停顿。
  • ·并发引用更新(Concurrent Update Reference):真正开始进行引用更新操作,这个阶段是与用户 线程一起并发的,时间长短取决于内存中涉及的引用数量的多少。并发引用更新与并发标记不同,它不再需要沿着对象图来搜索,只需要按照内存物理地址的顺序,线性地搜索出引用类型,把旧值改为 新值即可。
  • ·最终引用更新(Final Update Reference):解决了堆中的引用更新后,还要修正存在于GC Roots中的引用。这个阶段是Shenandoah的最后一次停顿,停顿时间只与GC Roots的数量相关。
  • ·并发清理(Concurrent Cleanup):经过并发回收和引用更新之后,整个回收集中所有的Region已再无存活对象,这些Region都变成Immediate Garbage Regions了,最后再调用一次并发清理过程来回收这些Region的内存空间,供以后新对象分配使用。
    在这里插入图片描述
    在这里插入图片描述

ZGC收集器

ZGC收集器是一款基于Region内存布局的,(暂时) 不设分代的,使用了读屏障、染色指针和内存多重映射等技术来实现可并发的标记-整理算法的,以低 延迟为首要目标的一款垃圾收集器。
ZGC也采用基于Region的堆内存布局,Region可以具有大、中、小三类容量:

  • ·小型Region(Small Region):容量固定为2MB,用于放置小于256KB的小对象。
  • ·中型Region(Medium Region):容量固定为32MB,用于放置大于等于256KB但小于4MB的对
    象。
  • ·大型Region(Large Region):容量不固定,可以动态变化,但必须为2MB的整数倍,用于放置 4MB或以上的大对象。每个大型Region中只会存放一个大对象,这也预示着虽然名字叫作“大型 Region”,但它的实际容量完全有可能小于中型Region,最小容量可低至4MB。大型Region在ZGC的实 现中是不会被重分配 的,因为复制一个大对象的代价非常高昂。
    在这里插入图片描述
    ZGC的运作过程:
    在这里插入图片描述
  • ·并发标记(Concurrent Mark):与G1、Shenandoah一样,并发标记是遍历对象图做可达性分析的 阶段,前后也要经过类似于G1、Shenandoah的初始标记、最终标记(尽管ZGC中的名字不叫这些)的 短暂停顿,而且这些停顿阶段所做的事情在目标上也是相类似的。与G1、Shenandoah不同的是,ZGC的标记是在指针上而不是在对象上进行的,标记阶段会更新染色指针中的Marked 0、Marked 1标志 位。
  • 并发预备重分配 (Concurrent Prepare for Relocate):这个阶段需要根据特定的查询条件统计得出 本次收集过程要清理哪些Region,将这些Region组成重分配集(Relocation Set)。重分配集与G1收集器 的回收集(Collection Set)还是有区别的,ZGC划分Region的目的并非为了像G1那样做收益优先的增量回收。相反,ZGC每次回收都会扫描所有的Region,用范围更大的扫描成本换取省去G1中记忆集的维护成本。因此,ZGC的重分配集只是决定了里面的存活对象会被重新复制到其他的Region中,里面的Region会被释放,而并不能说回收行为就只是针对这个集合里面的Region进行,因为标记过程是针对 全堆的。此外,在JDK12的ZGC中开始支持的类卸载以及弱引用的处理,也是在这个阶段中完成的。
  • ·并发重分配(Concurrent Relocate):重分配是ZGC执行过程中的核心阶段,这个过程要把重分
    配集中的存活对象复制到新的Region上,并为重分配集中的每个Region维护一个转发表(Forward
    Table),记录从旧对象到新对象的转向关系。得益于染色指针的支持,ZGC收集器能仅从引用上就明确得知一个对象是否处于重分配集之中,如果用户线程此时并发访问了位于重分配集中的对象,这次访问将会被预置的内存屏障所截获,然后立即根据Region上的转发表记录将访问转发到新复制的对象上,并同时修正更新该引用的值,使其直接指向新对象,ZGC将这种行为称为指针的“自愈”(SelfHealing)能力。这样做的好处是只有第一次访问旧对象会陷入转发,也就是只慢一次,对比
    Shenandoah的Brooks转发指针,那是每次对象访问都必须付出的固定开销,简单地说就是每次都慢,因此ZGC对用户程序的运行时负载要比Shenandoah来得更低一些。还有另外一个直接的好处是由于染
    色指针的存在,一旦重分配集中某个Region的存活对象都复制完毕后,这个Region就可以立即释放用于新对象的分配(但是转发表还得留着不能释放掉),哪怕堆中还有很多指向这个对象的未更新指针也没有关系,这些旧指针一旦被使用,它们都是可以自愈的。
  • ·并发重映射(Concurrent Remap):重映射所做的就是修正整个堆中指向重分配集中旧对象的所
    有引用,这一点从目标角度看是与Shenandoah并发引用更新阶段一样的,但是ZGC的并发重映射并不是一个必须要“迫切”去完成的任务,因为前面说过,即使是旧引用,它也是可以自愈的,最多只是第一次使用时多一次转发和修正操作。重映射清理这些旧引用的主要目的是为了不变慢(还有清理结束后可以释放转发表这样的附带收益),所以说这并不是很“迫切”。因此,ZGC很巧妙地把并发重映射阶段要做的工作,合并到了下一次垃圾收集循环中的并发标记阶段里去完成,反正它们都是要遍历所有对象的,这样合并就节省了一次遍历对象图的开销。一旦所有指针都被修正之后,原来记录新旧 对象关系的转发表就可以释放掉了。

现在用的多的应该是jdk7、8、9,后面会详细分析一下Parallel、CMS、G1以及jvm调优

摘自:《深入理解Java虚拟机》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值