JVM笔记《四》七个常见的垃圾收集器

垃圾收集器

一、 Serial (新生代)

1.单线程(收集期间需要暂停其他所有线程)

2.客户端模式下的默认收集器,也是很好的选择

优点:

1.简单高效(与其他收集器的单线程相比)

2.内存资源受限时,额外内存消耗最小

3.因为没有线程交互的开销,因此可获得最高的单线程效率

在这里插入图片描述

二、 ParNew(新生代)

是Serial的多线程版本。

多线程并行收集,其余与Serial相同

  1. 多线程并行收集(用户进程需等待)

  2. 目前唯一一个能与CMS收集器配合工作

    使用-XX:+UseConcMarkSweepGC参数,使用ParNew+CMS+Serial Old收集器组合,此时ParNew是新生代的默认收集器

  3. 默认开启的收集线程数与处理器核心数相同。

    可以使用-XX:ParallelGCThreads参数限制线程数

对垃圾收集时系统资源的高效利用很有好处。

在这里插入图片描述

JDK9后取消的组合:

  1. ParNew+CMS(未被取消,但不推荐作为服务端模式下的收集器组合)
  2. ParNew+Serial Old
  3. Serial+CMS

因此只有ParNew和CMS可以组合,也可以理解为ParNew并入了CMS

三、 Parallel Scanvenge(新生代)

1.基于标记-复制

2.并行收集

3.关注吞吐量 / ( + ) 用户代码时间/(用户代码时间+垃圾收集时间)

​ 适合在后台运算而不需要太多交互的任务

相关参数

  1. -XX:MaxGCPauseMillis

    大于0的毫秒数

    最大垃圾收集停顿时间(以牺牲年轻代大小为代价,每次停顿时间短,但导致GC更频繁,降低吞吐量)

  2. -XX:GCTimeRatio

    0-100 整数:垃圾收集时间占总时间的比率,这里原书概念有点乱不太好理解。

    但只要只要参数值N是通过公式: 1/1+N 来计算垃圾收集时间占总时间的比率,而N本身不是该比率

    如参数为49时:吞吐量=1-1/(1+49)=1-1/50 = 98% 1/50就是垃圾收集时间的占比

  3. -XX:UseAdaptiveSizePolicy

    开启该参数,不需要指定新生代大小(-Xmn)、-XX:SurvivorRatio、-XX:PretenureSizeThreshold等参数,只需要指定一个优化目标:如最大堆-Xmx,以及上面参数1或2,指定是关注停顿时间还是吞吐量,系统会动态的调整相关参数,此称为垃圾收集的自适应的调节策略

四、 Parallel Old(老年代)

Parallel Scavenge的老年代版本

  1. 多线程并发收集
  2. 基于标记整理
  3. 注重吞吐量(Parallel Scanvenge + Parallel Old)

在这里插入图片描述

五、 Serial Old(老年代)

Serial的老年代版本

1.单线程

2.标记整理

3.同Serial供客户端模式使用

4.作为CMS收集器失败时的后备预案

工作过程图参考一、 Serial收集器

六、 CMS(老年代)

关注停顿时间—> B/S等服务端模式中给用户给拱良好的交互体验

运作过程

1.初始标记 (Initial Mark)

2.并发标记(Concurrent Mark)

3.重新标记(Remark)

4.并发清除(Concurrent Sweep)

1.初始标记:标记与GCRoots直接关联的对象,速度快

2.并发标记:从GCRoots直接关联的对象开始遍历整个对象图,不暂停用户线程,可与GC线程并发运行,耗时长

3.重新标记:修正并发标记期间因用户线程导致标记产生变动的对象

4.并发清除:删除掉被标记死亡的对象,可与用户程序并发运行

在这里插入图片描述

缺点

  1. 对CPU资源敏感

    CMS默认启动的回收线程数是**(cpu数量+3)/4**。所以CPU数量少会导致用户程序执行速度降低较多。

  2. 无法处理浮动垃圾

    两个并发阶段因为用户程序仍要执行,因此会产生新的垃圾对象,而这些对象因出现在标记后,只能在下一次GC时回收,这些就是浮动垃圾。

    • CMS不能等到老年代对象满了才触发。

    • 参数-XX:CMSInitiatingOccupancyFraction:设置CMS触发的阈值,在jdk1.6中CMS默认启动阈值提升到了92%。

    • -XX:+UseCMSInitiatingOccupancyOnly :默认false,如果不使用,则只会使用一次上面设置的阈值,后面会自动调整;如果使用,则始终使用设定的阈值。

    可以将这两个参数搭配使用:

    -XX:CMSInitiatingOccupancyFraction=70 -XX:UseCMSInitiatingOccupancyOnly=true

    • CMS运行期间预留的内存无法满足程序的需要时,会出现”Concurrent Mode Failure“,然后会临时启用Serial Old收集器进行老年代的垃圾收集,这样停顿时间就很长了,所以参数值设置太高容易导致大量”Concurrent Mode Failure“。

    需要注意的是:

    intx CMSInitiatingOccupancyFraction = -1 {product}

    该参数的值在jdk1.8中默认值为-1,而92%的计算方式如下:

        void ConcurrentMarkSweepGeneration::init_initiating_occupancy(intx io, uintx tr) {
          assert(io <= 100 && tr <= 100, "Check the arguments");
          if (io >= 0) {
            _initiating_occupancy = (double)io / 100.0;
          } else {
            _initiating_occupancy = ((100 - MinHeapFreeRatio) +
                                     (double)(tr * MinHeapFreeRatio) / 100.0)
                                    / 100.0;
          }
        }
    
    
    1. 如果参数值大于等于0,则就按该百分比作为参数

    2. 如果小于0,则根据MinHeapFreeRatiotr按上面公式计算,MinHeapFreeRatio是堆大小达到这个该百分比时会缩小堆(-Xms=-Xmx时无效),tr即CMSTriggerRatio,查看可知这两个参数的默认值为:

      uintx MinHeapFreeRatio             = 40                           {manageable}
      uintx CMSTriggerRatio               = 80                           {product}
      

      因此得出的触发CMS的百分比为:((100-40)+(80*40)/100)/100=0.92=92%

  3. 存在空间碎片,因为基于标记-清除算法

    • -XX:+UseCMSCompactAtFullCollection :FullGC时进行碎片整理,独占无法并发,会使停顿时间变长,JDK9开始废弃
    • -XX:+CMSFullGCsBeforeCompaction 设置经过几次FullGC后进行碎片整理

总结

CMS触发阈值不能太大,否则会造成并发失效,也不能太小,会造成频繁触发CMS,而CMS的初始标记和重新标记(耗时长)会造成Stop The World,增大停顿时间,可设置为70

七、 Garbage First(G1)

1.面向服务端(JDK9开始作为服务器模式下的默认垃圾收集器,CMS则被Deprecate)

2.面向堆内存任何部分来组成回收集(CSet),衡量的标准变为哪块内存中存放的垃圾数量最多,回收收益最大

  • G1将堆分为大小相等的独立区域(Region),每个region都可以扮演eden、survivor和老年代,收集器能够针对不同角色的region采取不同的策略区处理,region是单次回收的最小单元,即每次回收的内存空间都是region的整数倍

  • Region中的Humongous(当做老年代看待)区域用于存储大对象,只要超过一个region容量一半的对象都可以称为大对象

    参数-XX:G1HeapRegionSize参数可以指定每个region大小,范围:1M~32M间的2的幂次数

    超过整个region的超大对象被放于连续的Homongous中

  • G1按照各个region垃圾的价值(即回收获得的空间与所需时间的经验值),维护一个优先级列表,依据用户设置的停顿时间优先回收价值大的region

几个问题

  1. G1使用记忆集避免整堆扫描

  2. 并发标记阶段保证收集线程和用户线程互不干扰的方式:

    G1通过原始快照方式保证用户线程改变引用关系时,原本的对象图结构不被破坏,保证标记的正确性。参考上文4.6(原始快照)

    • G1为每个region设计了两个TAMS(Top At Mark Start)指针
    • 划分一部分空间用于并发回收时产生的新对象的分配
    • 新对象地址必须在两个指针位置以上
    • G1默认在该地址以上的对象是存活的
    • 当分配对象的速度超过回收速度时,冻结用户线程,产生Full GC

运行过程

  1. 初始标记

    标记GCRoots直接关联的对象,修改TAMS指针值,让下个阶段用户进程并发运行时,能够在可用的region中分配新对象。同前面一样,需要STW,但耗时很短,且是借助MinorGC时同步完成,因此G1在该阶段不需要额外的停顿。

  2. 并发标记

    从GCRoots直接关联的对象对对象图做可达性分析,找出回收对象,耗时长。

    可与用户进程并发执行,扫描完成后,需重新处理SATB(原始快照)记录下的并发时引用变动的对象。

  3. 最终标记

    对用户线程作另一个短暂暂停,用于处理并发阶段结束后遗留下的少量SATB记录。

  4. 筛选回收

    • 更新region的统计数据
    • 对所有region按照回收价值和成本排序
    • 根据用户设定的停顿时间制定回收计划
    • 自由选择多个region作为回收集,将决定回收的region的存活对象复制到另一个空的region,再清理整个旧region
    • 暂停用户进程,可多条收集器并行执行。

    更新->排序->制定回收计划->选择回收集->复制清理(并行、STW)

    除了并发标记,都需要停止用户进程。

示意图如下:
在这里插入图片描述

特色和优点:

  1. 延迟可控情况下获得尽可能高的吞吐量。

  2. 用户可指定期望(要符合实际)的停顿时间

    默认为200ms,不能太低,太低会造成每次选择的回收集只占堆的很小一部分,回收速度跟不上分配速度,导致Full GC

  3. 分区(region)管理堆内存

  4. 按回收效益确定回收集

  5. 整体基于标记-整理,局部基于标记-复制,不会产生碎片

缺点(与CMS比)

  1. 内存占用

    因为每个region都需要维护一个卡表解决跨代引用问题,记忆集可能占整堆的20%以上。CMS则只有一份卡表。

  2. 额外执行负载高

    和CMS一样都使用写屏障维护卡表。

    除了和CMS一样使用写后屏障外(直接使用同步),G1还需要写前屏障实现原始快照算法,产生额外负担。(使用消息队列)

小内存应用CMS表现大概率由于G1

======================================================================

其他相关笔记:

JVM笔记(一)java内存区域与内存溢出以及对象的创建、布局和定位

JVM笔记(二)对象的生死与java的四大引用

JVM笔记(三)垃圾收集算法以及HotSpot的算法实现(安全点、记忆集与卡表、写屏障、三色标记等)

JVM笔记(五)类加载机制、类加载器和双亲委派机制

================================================================

参考:
《深入理解java虚拟机第三版》

发布了75 篇原创文章 · 获赞 13 · 访问量 8369

猜你喜欢

转载自blog.csdn.net/weixin_43696529/article/details/104884777
今日推荐