04-JVM垃圾收集器详解

1.垃圾收集器的种类

垃圾收集算法是内存回收的方法论,垃圾收集器是内存回收的具体实现工具。目前没有万能的垃圾收集器,需要根据具体的应用场景选择合适的垃圾收集器。

1.1Serial收集器(-XX:+UseSerialGC -XX:+UseSerialOldGC) 

Serial收集器即串行收集器,是最基本,历史最悠久的垃圾收集器。它是一个单线程的垃圾收集器。“单线程”的意义不仅仅意味着它只会使用一条垃圾收集线程去完成垃圾回收,更重要的是它在进行垃圾回收的时候必须暂停其他所有的工作线程(Stop The World),直到它结束。新生代采用“复制算法”,老年代采用“标记-整理”算法。

  STW会给带来不好的用户体验,因此JVM垃圾收集器设计人员还在不断的优化中。Serial垃圾收集器优于其他单线程收集器的点在于:简单高效(因为是单线程没有线程交互的开销,自然可以获得很高的单线程收集效率)。Serial Old收集器是Serial收集器的老版本,主要有两个用处:

  1. 在JDK1.5以及之前的版本配合Parallel Scavenge搭配使用。
  2. 作为CMS收集器的后备方案。

1.2 ParNew收集器(-XX:+UseParNewGC) 在年轻代使用

  ParNew收集器是Seral收集器的多线程版本,除了多线程以外,其他的行为(控制参数,收集算法,回收策略)跟Serial收集器一致。默认的收集线程数与CPU核数一致,也可以用参数

(-XX:ParallelGCThreads)来进行修改,从而指定收集线程数,一般不作更改。
新生代采用复制算法,老年代采用标记-整理算法。它是许多运行在Server模式下的虚拟机的首要选择,除了Serial收集器外,只有它能与CMS收集器(真正意义上的并发收集器)配合使用。

1.3 Parallel Scavenge收集器(-XX:+UseParallelGC(年轻代),-XX:+UseParallelOldGC(老年代)) 

  Parallel Scavenge收集器类似于ParNew收集器,是在Server模式(内存>2G,2个CPU)下的默认收集器.。Parallel Scavenge收集器关注点是吞吐量(高效的利用CPU),而CMS收集器关注的是提高用户体验(用户线程的停顿时间)。所谓吞吐量就是CPU中用于运行用户代码的时间与CPU总消耗时间的比值。Parallel Scavenge收集器提供了很多参数供用户找到最合适的停顿时间或者最大吞吐量。如果对于收集器的运作不太了解可以把内存优化交给虚拟机去完成。

年轻代使用复制算法,老年代使用标记-整理算法。

Parallel Old收集器是Parallel Scavenge收集器的老年代版本。使用多线程和“标记-整理”算法。在注重吞吐量以及CPU资源的场合,都可以优先考虑 Parallel Scavenge收集器和ParallelOld收集器.

 1.4 CMS收集器(-XX:+UseConcMarkSweepGC(old)) 在老年代使用

  CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它非常符合在注重用户体验的应用上使用,它是HotSpot虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。从名字中的Mark Sweep这两个词可以看出,CMS收集器是一种 “标记-清除”算法实现的,它的运作过程相比于前面几种垃圾收集器来说更加复杂一些。整个过程分为四个步骤:

  1. 初始标记: 暂停所有的其他线程,并记录下gc roots(线程栈的本地变量,静态变量,本地方法栈的变量)直接能引用的对象,速度很快 ;
  2. 并发标记: 同时开启GC和用户线程,用一个闭包结构去记录可达对象(用初始标记的GC Root根)。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以GC线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方。
  3. 重新标记: 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短
  4. 并发清理: 开启用户线程,同时GC线程开始对未标记的区域做清扫。

  CMS垃圾收集器的优点是:并发收集、低停顿。但是存在下面几个缺点:

  1. 对CPU资源敏感(会和服务抢资源);
  2. 无法处理浮动垃圾(在并发清理阶段会产生新的垃圾,这种浮动垃圾只能等到下一次GC的时候才能够被清除);
  3. 它使用的“标记-清除”算法会产生大量的空间碎片。可以通过-XX:+UseCMSCompactAtFullCollection,可以让jvm在标记清除以后再做整理。
  4. 执行过程的不确定性,会存在上一次垃圾回收还没有收集完就会触发下一次垃圾回收,特别是在并发标记和并发清理阶段,一边回收一边运行,也许还没有回收完就触发了下一次的full gc,也就是“concurrent mode failure”此时会进入STW,使用Serial Old垃圾收集器回收。
CMS的相关参数
  1. -XX:+UseConcMarkSweepGC:启用cms;
  2. -XX:ConcGCThreads:并发的GC线程数;
  3. -XX:+UseCMSCompactAtFullCollection:FullGC之后做压缩整理(减少碎片);
  4. -XX:CMSFullGCsBeforeCompaction:多少次FullGC之后压缩一次,默认是0,代表每次FullGC后都会压缩一次;
  5. -XX:CMSInitiatingOccupancyFraction: 当老年代使用达到该比例时会触发FullGC(默认是92,这是百分比);
  6. -XX:+UseCMSInitiatingOccupancyOnly:只使用设定的回收阈值(-XX:CMSInitiatingOccupancyFraction设定的值),如果不指定,JVM仅在第一次使用设定值,后续则会自动调整;
  7. -XX:+CMSScavengeBeforeRemark:在CMS GC前启动一次minor gc,目的在于减少老年代对年轻代的引用,降低CMS GC的标记阶段时的开销,一般CMS的GC耗时 80%都在remark阶段 ;

1.5 G1收集器(-XX:+UseG1GC)

G1(Garbage-First)收集器是一款面向服务器的垃圾收集器,主要针对配备多颗处理器和大量内存的机器,同时满足高概率GC停顿时间,高吞吐量的性能特征。G1收集器打破了堆的原始结构(Eden,From,To,Old)区域,而是采用了一种新的更加灵活的堆结构,类似于快递存储箱。G1将堆划分为大小相等的独立模块(Region),JVM最多可以拥有2048个Region。每个Region的大小就是堆大小/2048.假如堆的大小为2G,那么Region的大小就是1M.也可以使用-XX:G1HeapRegionSize参数来指定Region大小。但是一般选择默认值。G1保留了年轻代和老年代的概念,只不过年轻代和老年代不再是以前的连续区域,而是Region集合。

                                             G1堆结构

 默认年轻代对堆的占比是5%,加入堆的大小是2048M,那么年轻代就约占102M,也就是说有大概102个Region,可以通过参数“-XX:G1NewSizePercent” 参数来变更初始占比,在系统运行中JVM会不断的给年轻代分配更多的内存。但是最多的新生代占比不能超过60%,可以通过参数“-XX:G1MaxNewSizePercent”来调整最大占比,Eden区域与Survivor区域的占比依旧是8:1:1。

  一个Region可能之前时候S区,经过垃圾回收以后变成了老年代区域,也就是说Region区域的功能是动态变化的。就像超市的私人物品存储箱一样,一开始是A的,A拿走了属于自己的东西以后,这个储物箱就可以被其他人使用了,可以是任何人的。

G1的对象在什么时候进入老年代等回收机制都是跟之前的堆一样的。唯一不同的是G1对大对象的处理。当一个对象大小超过Region区域的50%,那么这个对象就会被判定为大对象,就会被放在Humongous区域里面,而不是像之前的堆一样直接放在老年代中。而是一个专门的Humongous区域里。如果一个对象太大,会横跨多个Region区域来存放。Humongous区域专门用来存储短期巨型对象,当JVM进行fullGC的时候除了收集老年代,年轻代以外还会收集Humongous区域。

  G1收集器的进行垃圾回收的步骤如下:

  1. 初始标记:STW,停止所有工作线程,记录下GC Roots直接能引用的对象,速度很快
  2. 并发标记:同CMS
  3. 最终标记:STW,同CMS
  4. 筛选回收:STW,在筛选回收开始之前,先对Region区域的回收价值和成本进行排序,然后根据用户期待的GC停顿时间(-XX:MaxGCPauseMillis)来制定回收计划。回收算法主要使用的是复制算法。比如说此时老年代有1000个Region都满了,需要进行回收,此时用户期待的停顿时间是200ms,根据这个停顿时间以及成本等计算,回收其中的600个Region区域刚好是200ms,那么就回收这600个Region区域。因为采用的是复制算法,因此不会有太多的内存碎片。

G1收集器在后台维护了一个优先列表,每次根据允许的时间,优先选择回收价值最大的Region。比如一个Region花200ms可以回收300M垃圾,另外一个可以在100ms回收400M垃圾,那么在G1垃圾回收的时候会优先选择后者。这种使用Region划分内存空间以及有优先级的区域回收方式,保证了G1收集器在有限的时间内尽可能高的垃圾回收效率。

G1收集器特点:

  • 并行与并发:G1能够充分利用CPU,多核的硬件环境,多个CPU来缩短STW的时间。部分其他的收集器需要停顿java程序来执行GC动作,G1收集器仍然可以通过并发的方式让java程序继续执行。
  • 分代收集:虽然G1不需要其他收集器的配合就可以管理GC堆,但是它仍然保留了分代的概念
  • 空间整合:从整理上来看,G1使用的是“标记-整理”算法,局部上看使用的是“复制”算法。
  • 可预测的停顿:G1与CMS都是关注于降低停顿时间,G1对于降低停顿时间优于CMS的点在于:G1除了追求低停顿之外,还建立了可停顿时间预测模型,能让用户通过参数"-XX:MaxGCPauseMillis"控制垃圾回收完成时间。
G1收集器参数设置:
  • -XX:+UseG1GC:使用G1收集器
  • -XX:ParallelGCThreads:指定GC工作的线程数量
  • -XX:G1HeapRegionSize:指定分区大小(1MB~32MB,且必须是2的幂),默认将整堆划分为2048个分区
  • -XX:MaxGCPauseMillis:目标暂停时间(默认200ms)
  • -XX:G1NewSizePercent:新生代内存初始空间(默认整堆5%)
  • -XX:G1MaxNewSizePercent:新生代内存最大空间
  • -XX:TargetSurvivorRatio:Survivor区的填充容量(默认50%),Survivor区域里的一批对象(年龄1+年龄2+年龄n的多个年龄对象)总和超过了Survivor区域的50%,此时就会把年龄n(含)以上的对象都放入老年代
  • -XX:MaxTenuringThreshold:最大年龄阈值(默认15)
  • -XX:InitiatingHeapOccupancyPercent:老年代占用空间达到整堆内存阈值(默认45%),则执行新生代和老年代的混合收集(MixedGC),比如我们之前说的堆默认有2048个region,如果有接近1000个region都是老年代的region,则可能就要触发MixedGC了
  • -XX:G1HeapWastePercent(默认5%): gc过程中空出来的region是否充足阈值,在混合回收的时候,对Region回收都是基于复制算法进行的,都是把要回收的Region里的存活对象放入其他Region,然后这个Region中的垃圾对象全部清理掉,这样的话在回收过程就会不断空出来新的Region,一旦空闲出来的Region数量达到了堆内存的5%,此时就会立即停止混合回收,意味着本次混合回收就结束了。
  • -XX:G1MixedGCLiveThresholdPercent(默认85%) region中的存活对象低于这个值时才会回收该region,如果超过这个值,存活对象过多,回收的的意义不大。
  • -XX:G1MixedGCCountTarget:在一次回收过程中指定做几次筛选回收(默认8次),在最后一个筛选回收阶段可以回收一会,然后暂停回收,恢复系统运行,一会再开始回收,这样可以让系统不至于单次停顿时间过长。

 G1垃圾收集分类:

YoungGC

  当Eden区满的时候,G1不着急做YoungGC,而是计算回收Eden区需要的时间是否比设置的参数“-XX:MaxGCPauseMills”小,如果比这个参数小,那么G1会增加年轻代的Region,存放新的对象,直至下一次Eden区满的时候重复以上步骤,如果此时YoungGCd的收集时间跟设置参数接近,那么就执行YoungGC。

 MixedGC

   当老年代占用空间达到整堆内存阈值(默认45%:-XX:InitiatingHeapOccupancyPercent),则执行新生代和老年代的混合收集(MixedGC)(根据期望的GC停顿时间确定Old区域的垃圾收集优先顺序),回收的是所有的Young部分Old区域以及大对象区域。正常情况下G1的垃圾收集都是先做MixedGC,主要使用复制算法,将回收Region中的存活对象复制到其他的Region中去,拷贝过程中如果发现没有足够的空Region来存储拷贝对象,就会触发FullGC.

FullGC

STW,然后用单线程进行标记,整理和压缩整理,好空闲出更多的Region区域来供下一次的MixedGC使用,这个过程比较耗时。

G1垃圾收集器优化建议:

G1垃圾收集器优化的核心在于调节参数:-XX:MaxGCPauseMills,保证YoungGC不会太频繁,同时考虑YoungGC过后的存活对象有多少,避免存活对象太多太快的进行入老年代,频繁的出发MixedGC。

 

原文地址:https://www.cnblogs.com/yatou-blog/p/11995594.html