【jvm】垃圾收集器

1,242 阅读7分钟

在了解垃圾收集算法之后,我们要看一下基于垃圾收集算法实现的垃圾收集器是如何实现的,本文出现的垃圾收集器都是HotSpot虚拟机提供的实现

堆内存是垃圾收集器主要回收垃圾对象的地方,堆内存可以根据对象生命周期的不同细分为新生代和老年代,每个年代都可以选择适合的垃圾收集算法,新生代使用复制算法,老年代使用标记清除或者标记整理算法

HotSpot虚拟机提供了7种垃圾收集器,其中适用于新生代的三种,老年代的三种,还有一种新生代老年代都适用

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

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

都适用的收集器:G1收集器

所有垃圾收集器组合情况如下:

图中连线的收集器表明可以一起搭配使用

Serial 收集器

Serial收集器曾是jdk1.3之前新生代收集器的唯一选择,串行收集器,单线程工作,所以效率高,但是会产生Stop The World,适用于堆内存比较小的时候使用,使用复制算法回收垃圾对象

参数设置:-XX:+UseSerialGC 使用Serial收集器

ParNew 收集器

parNew收集器是Serial收集器的多线程版本,并行收集器,除了多线程收集外,其余功能与Serial收集器基本一致,在新生代中,除了Serial收集器,只有ParNew收集器可以与CMS收集器一起使用,所以目前为止这个收集器是新生代最常用的收集器

参数设置:

  • -XX:+UseParNewGC:使用ParNew收集器
  • -XX:ParallelGCThreads:用来限制垃圾收集的线程数

Parallel Scavenge 收集器

并行收集器,同样使用复制算法,与其余两种年轻代收集器不同的地方在于它的关注点,Serial和ParNew收集器的关注点在于尽可能的缩短Stop The World的时间,而Parallel Scavenge收集器的关注点在于吞吐量

吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间),比如虚拟机运行100分钟,垃圾收集时间是1分钟,那么吞吐量就是99%

参数设置:

  • -XX:+UseParallelGC:使用Parallel收集器
  • -XX:MaxGCPauseMillis:控制最大垃圾收集停顿时间
  • -XX:GCTimeRatio:设置吞吐量大小,默认为99,允许1%的垃圾收集时间
  • -XX:+UseAdaptiveSizePolicy:自适应调节,设置之后就不需要手动设置新生代大小

Serial Old 收集器

Serial Old收集器是Serial的老年代版本,单线程收集器,使用标记整理算法

可以与Parallel Scavenge收集器搭配使用,也可以作为CMS收集器发生Concurrent Mode Failure时老年代的备选方案使用

Parallel Old 收集器

Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用标记整理算法

在注重吞吐量及CPU资源敏感的场景,可以用考虑使用Parallel Scavenge+Parallel Old收集器

参数设置:-XX:+UseParallelOldGC:使用Parallel Scavenge + Parallel Old收集器

CMS收集器

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,目前使用最多的垃圾收集器,使用这类收集器的应用重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验

使用标记清除算法,共4个步骤

  1. 初始标记(CMS initial mark)
  2. 并发标记(CMS concurrent mark)
  3. 重新标记(CMS remark)
  4. 并发清除(CMS concurrent sweep)

初始标记、重新标记这两个步骤仍然需要Stop The World,初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快,并发标记阶段就是进行GC Roots Tracing的过程,而重新标记阶段则是为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短

耗时较长的并发标记和并发清除的线程可以与用户线程一起工作,所以总体上来说,CMS收集器的内存回收过程是与用户线程一起并发地执行

优点:并发收集,低停顿

缺点:

  1. 对CPU资源敏感

    CMS默认启动的回收线程数量是(CPU数量+3)/4,当CPU数量小于4时,并发操作时会占用一半的CPU去进行垃圾收集

  2. 无法处理浮动垃圾,可能回导致Concurrent Mode Failure失败而导致领一次Full GC的产生

    CMS收集器触发GC时需要预留一部分空间供并发收集时使用,通过设置参数-XX:CMSInitiatingOccupancyFraction的值来提高触发的百分比

    当预留的空间无法满足需求时就会发生Concurrent Mode Failure失败,这时,java虚拟机就会采用Serial Old收集器回收老年代垃圾,代价就是停顿时间变长

    所以-XX:CMSInitiatingOccupancyFraction参数设置的过高容易频繁触发Concurrent Mode Failure失败,导致性能降低

  3. 产生大量空间碎片

    设置-XX:+ UseCMSCompactAtFullCollection参数,开启空间压碎,默认是开启的,开启后FUll GC时会进行内存整理,空间碎片问题解决,但是停顿时间就会变长

    设置-XX:+ CMSFullGCsBeforeCompaction参数,设置进行几次Full GC后,进行一次碎片整理,默认是0,表示每次进入Full GC时都进行碎片整理

参数设置:

  • -XX:+UseConcMarkSweepGC:使用CMS收集器 + PerNew收集器
  • -XX:ParallelCMSThreads:设定CMS的线程数量

G1 收集器

G1是一款面向服务端应用的垃圾收集器,HotSpot开发团队赋予它的使命是未来可以替换掉JDK1.5中发布的CMS收集器

G1收集器采用标记整理算法,所以G1收集器不会产生空间碎片

G1特点:

  • 并行与并发

    G1可以充分的利用多CPU和多核环境下的硬件优势用来缩短Stop The World的停顿时间

  • 可预测的停顿

    降低停顿时间是G1和CMS的共同关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为N毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒,这几乎已经是实时Java(RTSJ)的垃圾收集器的特征了

G1收集器将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔阂了,它们都是一部分(可以不连续)Region的集合

G1的新生代收集跟ParNew类似,当新生代占用达到一定比例的时候,开始出发收集;和CMS类似,G1收集器收集老年代对象会有短暂停顿

收集步骤:

  1. 初始标记

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

  2. 并发标记

    从GC Roots出发,开始对堆中对象进行可达性分析,找出存活对象

  3. 最终标记

    为了修正并发标记阶段因用户程序继续运行而导致标记产生变动的那一部分标记记录

  4. 筛选回收

    对各个Region的回收价值和成本进行排序,根据用户所期待的GC停顿时间来制定回收计划

参考: jvm系列(三):java GC算法 垃圾收集器

深入理解java虚拟机第二版

更多阅读:chenmingyu.top/categories/

欢迎关注我的公众号