美文网首页02 java难点
垃圾收集器与内存分配策略(三)——七种垃圾收集器

垃圾收集器与内存分配策略(三)——七种垃圾收集器

作者: 路远处幽 | 来源:发表于2019-07-14 12:18 被阅读0次

    如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。Java虚拟机规范中对垃圾收集器应该如何实现并没有任何规定,因此不同厂商、不同版本的虚拟机所提供的垃圾收集器可能会有很大差别。这里讨论的收集器基于JDK 1.7 Update 14之后的HotSpot虚拟机(在这个版本中正式提供了商用的G1收集器,之前G1仍然处于试验状态,JDK11已经作为默认的收集器),这个虚拟机所包含的所有收集器如下图:


    Se

    一、Serial收集器(新生代)

    最原生的收集器, jdk1.3以前唯一的选择.
    单线程收集器. 这里并不是指使用一个CPU或一条收集线程去完成垃圾收集工作, 而是指它在收集垃圾时, 必须暂停用户工作线程(Stop The World).


    image.png
    • 单线程
      只会使用一个CPU或一条GC线程进行GC,并且在GC过程中暂停其他所有的工作线程,因此用户的请求或图形化界面会出现卡顿

    • 适合Client模式
      一般客户端应用所需内存较小,不会创建太多的对象,而且堆内存不大,因此GC时间比较短,即使在这段时间停止一切用户线程,也不会感到明显停顿

    • 简单高效
      由于Serial收集器只有一条GC线程,避免了线程切换的开销

    • 采用"复制"算法

    二、ParNew收集器(新生代)

    ParNew是Serial的多线程版本


    image.png
    • 多线程并行执行
      ParNew由多条GC线程并行地进行垃圾清理.
      但清理过程仍然需要暂停一切其他用户线程.
      但由于有多条GC线程同时清理,清理速度比Serial有一定的提升
    • 适合多CPU服务器的环境
      由于使用多线程,是许多运行在 server 模式下的虚拟机首选的新生代收集器
      与Serial性能对比ParNew和Serial唯一区别就是使用了多线程垃圾回收,在多CPU的环境下性能比Serial会有一定程度的提升。但线程切换需要额外的开销,因此在单CPU环境中表现不如Serial,双CPU环境也不一定就比Serial高效。默认开启的收集线程数与CPU数量相同。
    • 采用"复制"算法
    • 追求“降低停顿时间”
      和Serial相比,ParNew使用多线程的目的就是缩短GC时间,从而减少用户线程被停顿的时间。
    • 使用参数
      使用参数 -XX:+UseParNewGC
      限制线程数 -XX:ParallelGCThreads

    三、Parallel Scavenge收集器(新生代)

    吞吐量优先收集器,Parallel Scavenge和ParNew一样都是并行的多线程、新生代收集器,都使用"复制"算法(Stop-The-World)进行垃圾回收。
    ParNew收集器追求降低GC时用户线程的停顿时间,适合交互式应用,良好的反应速度提升用户体验。
    Parallel Scavenge追求可控的CPU吞吐量,能够在较短的时间内完成指定任务,适合不需太多交互的后台运算。


    image.png
    • 优点:可以精确控制吞吐量
    • 缺点:原本10s收集一次, 每次停顿100ms, 设置完参数之后可能变成5s收集一次, 每次停顿70ms. 停顿时间变短, 但收集次数变多

    四、Serial Old收集器(老年代)

    Serial的老年代版本,都是单线程收集器,GC时只启动一条GC线程,因此都适合客户端应用.
    它们唯一的区别就是:Serial Old工作在老年代,使用"标记-整理"算法;Serial工作在新生代,使用"复制"算法。

    五、Parallel Old收集器(老年代)

    Parallel Scavenge收集器的老年代版本。
    在jdk1.6之前, 如果新生代选择了Parallel Scaenge收集器, 老年代除了Serial Old(PS Mark Sweep)收集器外别无选择.( 上面说过, Parallel Scavenge收集器无法与CMS-Concurrent Mark Sweep收集器搭配工作)。但是现在可以使用Parallel Scavenge + Parallel Old组合. 而不必像之前那样Prallel Scavenge + Serial Old组合.

    六、CMS收集器(老年代)

    (Concurrent Mark Sweep Collector) : 低延迟为先!
    回收停顿时间比较短、目前比较常用的垃圾回收器。它通过初始标记(InitialMark)、并发标记(Concurrent Mark)、重新标记( Remark)、并发清除( Concurrent Sweep )四个步骤完成垃圾回收工作。


    image.png

    有两步需要"Stop The World":初始标记和重新标记。

    • 初始标记 (Initial Mark)
      停止一切用户线程,仅使用一条初始标记线程对所有与GC Roots直接相关联的 老年代对象进行标记,速度很快
    • 并发标记 (Concurrent Marking Phase)
      使用多条并发标记线程并行执行,并与用户线程并发执行.此过程进行可达性分析,标记所有这些对象可达的存货对象,速度很慢
    • 重新标记 ( Remark)
      因为并发标记时有用户线程在执行,标记结果可能有变化
      停止一切用户线程,并使用多条重新标记线程并行执行,重新遍历所有在并发标记期间有变化的对象进行最后的标记.这个过程的运行时间介于初始标记和并发标记之间
    • 并发清除 (Concurrent Sweeping)
      只使用一条并发清除线程,和用户线程们并发执行,清除刚才标记的对象
      这个过程非常耗时

    CMS的缺点:

    • 吞吐量低
      由于CMS在GC过程用户线程和GC线程并行,从而有线程切换的额外开销
      因此CPU吞吐量就不如在GC过程中停止一切用户线程的方式来的高
    • 无法处理浮动垃圾,导致频繁Full GC
      由于垃圾清除过程中,用户线程和GC线程并发执行,也就是用户线程仍在执行,那么在执行过程中会产生垃圾,这些垃圾称为"浮动垃圾"
      如果CMS在GC过程中,用户线程需要在老年代中分配内存时发现空间不足,就需再次发起Full GC,而此时CMS正在进行清除工作,因此此时只能由Serial Old临时对老年代进行一次Full GC
    • 使用"标记-清除"算法产生碎片空间
      由于CMS使用了"标记-清除"算法, 因此清除之后会产生大量的碎片空间,不利于空间利用率.不过CMS提供了应对策略:
      1、开启-XX:+UseCMSCompactAtFullCollection
      开启该参数后,每次FullGC完成后都会进行一次内存压缩整理,将零散在各处的对象整理到一块儿.但每次都整理效率不高,因此提供了以下参数.
      2、设置参数-XX:CMSFullGCsBeforeCompaction
      本参数告诉CMS,经过了N次Full GC过后再进行一次内存整理.

    CMS应用场景:
    目前很大一部分的Java应用集中在互联网网站或B/S系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停时间最短,以给用户带来较好的体验。CMS收集器非常符合这类应用的需求。

    七、G1收集器(万能收集器)

    Hotspot 在JDK7中推出了新一代 G1 ( Garbage-First Garbage Collector )垃圾回收,通过

    -XX:+UseG1GC

    参数启用
    和CMS相比,Gl具备压缩功能,能避免碎片向題,G1的暂停时间更加可控。性能总体还是非常不错的,G1是当今最前沿的垃圾收集器成果之一.


    image.png

    G1收集器相关概念

    • G1的内存模型
      没有新生代和老年代的概念,而是将Java堆划分为一块块独立的大小相等的Region.
      当要进行垃圾收集时,首先估计每个Region中的垃圾数量,每次都从垃圾回收价值最大的Region开始回收,因此可以获得最大的回收效率
    • Remembered Set
      一个对象和它内部所引用的对象可能不在同一个Region中,那么当垃圾回收时,是否需要扫描整个堆内存才能完整地进行一次可达性分析?
      当然不是,每个Region都有一个Remembered Set,用于记录本区域中所有对象引用的对象所在的区域,从而在进行可达性分析时,只要在GC Roots中再加上Remembered Set即可防止对所有堆内存的遍历.

    G1收集器特点:

    • 并行与并发:G1能充分利用多CPU,多核环境下的硬件优势,使用多个CPU来缩短Stop-The-World停顿时间,部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让Java程序继续执行。
    • 分代收集:与其他收集器一样,分代概念在G1中得以保留。
    • 空间整合:与CMS的“标记-清理”算法不同,G1从整体来看是基于“标记-整理”算法实现的收集器,从局部上来看是基于“复制”算法实现的,这两种算法都不会产生内存空间碎片。
    • 可预测的停顿:这是G1相对于CMS的另一大优势,降低停顿时间是G1和CMS共同关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集器上的时间不得超过N毫秒,这几乎已经是实时Java(RTSJ)的垃圾收集器的特征了。

    G1垃圾收集过程

    • 初始标记
      标记与GC Roots直接关联的对象,停止所有用户线程,只启动一条初始标记线程,这个过程很快.
    • 并发标记
      进行全面的可达性分析,开启一条并发标记线程与用户线程并行执行.这个过程比较长.
    • 最终标记
      标记出并发标记过程中用户线程新产生的垃圾.停止所有用户线程,并使用多条最终标记线程并行执行.
    • 筛选回收
      回收废弃的对象.此时也需要停止一切用户线程,并使用多条筛选回收线程并行执行.

    S0/S1的功能由G1中的Survivor region来承载,通过GC日志可以观察到完整的垃圾回收过程如下,其中就有Survivor regions的区域从0个到1个


    image.png

    红色标识的为G1中的四种region,都处于Heap中.
    G1执行时使用4个worker并发执行,在初始标记时,还是会触发STW,如第一步所示的Pause

    相关文章

      网友评论

        本文标题:垃圾收集器与内存分配策略(三)——七种垃圾收集器

        本文链接:https://www.haomeiwen.com/subject/slubkctx.html