当前位置: 首页 > >

jvm各种回收器,各自优缺点,重点CMS、G1

发布时间:

串行、并行与并发

下面2个名词都是并发编程中的概念,在谈论垃圾收集器的上下文语境中,它们可以解释如下:


串行:单个线程执行垃圾回收,并且此时用户线程仍然处于等待状态。并行:指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。并发:指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),用户程序在继续运行,而垃圾收集程序运行于另一个CPU上。
新生代回收器:SerialGC ParNewGc ParallelScavengeGC
名称?串行/并行/并发回收算法适用场景可以与cms配合
SerialGC串行复制单cpu
ParNewGC并行复制多cpu
ParallelScavengeGC并行复制多cpu且关注吞吐量


Serial(串行GC)收集器
? ? ? ? Serial收集器是一个新生代收集器,单线程执行,使用复制算法。它在进行垃圾收集时,必须暂停其他所有的工作线程(用户线程)。是Jvm client模式下默认的新生代收集器。对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集效率,适用于单cpu机器的场景。在用户的桌面应用场景中,即Client模式下的虚拟机来说是一个很好的选择。
ParNew(并行GC)收集器
? ? ? ? ParNew收集器其实就是serial收集器的多线程版本,除了使用多条线程进行垃圾收集之外,其余行为与Serial收集器一样。它是许多运行在Server模式下的虚拟机中首选的新生代收集器,其中有一个与性能无关但很重要的原因是,除了Serial收集器外,目前只有它能与CMS收集器配合工作。ParNew在单CPU环境下绝对不会有比Serial收集器更好的效果,甚至由于存在线程交互的开销,该收集器在通过超线程技术实现的两个CPU的环境中都不能百分百保证可以超越Serial收集器。当然,随着可以使用的CPU的数量的增加,它对GC时系统资源的有效利用还是很有好处的。
Parallel Scavenge(并行回收GC)收集器
? ? ? ? Parallel Scavenge收集器也是一个新生代收集器,它也是使用复制算法的收集器,又是并行多线程收集器。parallel Scavenge收集器的特点是它的关注点与其他收集器不同,CMS等收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间,而parallel Scavenge收集器的目标则是达到一个可控制的吞吐量。吞吐量= 程序运行时间/(程序运行时间 + 垃圾收集时间),虚拟机总共运行了100分钟。其中垃圾收集花掉1分钟,那吞吐量就是99%。由于于吞吐量关系密切,Parallel Scavenge收集器也经常被称为“吞吐量优先”收集器。Parallel Scavenge收集器有一个参数-XX:UseAdaptiveSizePolicy,当这个参数打开,虚拟机会根据当前系统的运行状况收集性能监控信息,动态调整一些如新生代大小、Eden与Survivor区的比例等等细节参数。这种自适应调节策略也是Parallel Scavenge收集器与ParNew收集器的一个重要区别。


三种老生代回收器
名称?串行/并行/并发回收算法适用场景
SerialOldGC串行标记整理单cpu
ParNewOldGC并行标记整理多cpu
CMS并发,几乎不会暂停用户线程标记清除多cpu且与用户线程共存

Seral Old(串行GC)收集器


?? ? ? ? Serial Old是Serial收集器的老年代版本,它同样使用一个单线程执行收集,使用“标记-整理”算法。主要使用在Client模式下的虚拟机。如果在Server模式下,那么它还有两大用途:一种用途是在JDK1.5以及之前的版本中与Parallel Scavenge收集器搭配使用,另一种用途是作为CMS收集器的后备预案,在并并发手机发生Concurrent Mode Failure时使用。
Parallel Old(并行GC)收集器
? ? ? ? Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。在注重吞吐量以及CPU资源敏感的场合,都可以优先考虑Parallel Scavenge收集器加Parallel Old收集器。
CMS(并发GC)收集器
? ? ? ? CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,适用于集中在互联网站或者B/S系统的服务端的Java应用。CMS收集器是基于“标记-清除”算法实现的,整个收集过程大致分为4个步骤:
②.并发标记(CMS concurrenr mark)
③.重新标记(CMS remark)
④.并发清除(CMS concurrent sweep)
? ? ?其中初始标记、重新标记这两个步骤任然需要停顿其他用户线程。初始标记仅仅只是标记出GC ROOTS能直接关联到的对象,速度很快,并发标记阶段是进行GC ROOTS 根搜索算法阶段,会判定对象是否存活。而重新标记阶段则是为了修正并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间会被初始标记阶段稍长,但比并发标记阶段要短。
? ? ?由于整个过程中耗时最长的并发标记和并发清除过程中,收集器线程都可以与用户线程一起工作,所以整体来说,CMS收集器的内存回收过程是与用户线程一起并发执行的。
  CMS收集器对CPU资源非常敏感。在并发阶段,虽然不会导致用户线程停顿,但是会占用CPU资源而导致引用程序变慢,总吞吐量下降。CMS默认启动的回收线程数是:(CPU数量+3) / 4。
  CMS收集器无法处理浮动垃圾,可能出现“Concurrent Mode Failure“,失败后而导致另一次Full ?GC的产生。由于CMS并发清理阶段用户线程还在运行,伴随程序的运行自热会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法在本次收集中处理它们,只好留待下一次GC时将其清理掉。这一部分垃圾称为“浮动垃圾”。也是由于在垃圾收集阶段用户线程还需要运行,
即需要预留足够的内存空间给用户线程使用,因此CMS收集器不能像其他收集器那样等到老年代几乎完全被填满了再进行收集,需要预留一部分内存空间提供并发收集时的程序运作使用。在默认设置下,CMS收集器在老年代使用了68%的空间时就会被激活,也可以通过参数-XX:CMSInitiatingOccupancyFraction的值来提供触发百分比,以降低内存回收次数提高性能。要是CMS运行期间预留的内存无法满足程序其他线程需要,就会出现“Concurrent Mode Failure”失败,这时候虚拟机将启动后备预案:临时启用Serial Old收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了。所以说参数-XX:CMSInitiatingOccupancyFraction设置的过高将会很容易导致“Concurrent Mode Failure”失败,性能反而降低。
  最后一个缺点,CMS是基于“标记-清除”算法实现的收集器,使用“标记-清除”算法收集后,会产生大量碎片。空间碎片太多时,将会给对象分配带来很多麻烦,比如说大对象,内存空间找不到连续的空间来分配不得不提前触发一次Full ?GC。为了解决这个问题,CMS收集器提供了一个-XX:UseCMSCompactAtFullCollection开关参数,用于在Full ?GC之后增加一个碎片整理过程,还可通过-XX:CMSFullGCBeforeCompaction参数设置执行多少次不压缩的Full ?GC之后,跟着来一次碎片整理过程。


CMS晋升失败

晋升失败同样是老年代导致的问题。


CMS开启新生代垃圾收集的时候,判断老年代似乎有足够空间容纳所有晋升对象。


然而晋升的时候才发现老年代的空间竟然都是碎片化的,根本容纳不了一个完整的晋升对象。


剩下出路只有内存整理。所有应用运行的线程停止,CMS开始对老年代进行整理和压缩。


空间压缩要通过移动里面的对象,令这些对象排列好,所以晋升失败比不需要移动对象的并发失效更加浪费时间。


完成清理的堆空间变得规整和空余,继续运行应用


因为CMS本身不能规整Compat内存,只能退化到SerialGC来做



G1收集器

? ? ?参考:?https://blog.csdn.net/h2604396739/article/details/107957569


JDK11增加了两种全新的 GC 方式

Epsilon GC,简单说就是个不做垃圾收集的 GC,似乎有点奇怪,有的情况下,例如在进行性能测试的时候,可能需要明确判断 GC 本身产生了多大的开销,这就是其典型应用场景。
ZGC,这是 Oracle 开源出来的一个超级 GC 实现,具备令人惊讶的扩展能力,比如支持 T bytes 级别的堆大小,并且保证绝大部分情况下,延迟都不会超过 10 ms。虽然目前还处于实验阶段,仅支持 Linux 64 位的*台,但其已经表现出的能力和潜力都非常令人期待。


两代回收器之间的配合关系

如下图:注意新生代的parNew和老年代的ParallelOld不可搭配使用。



声明100M大小的byte数组,jvm剩余空间大于100M,报了OOM,什么原因?
数组需要连续空间100M
1 尝试放到新生代,新生代GC,发现放不下;超大对象不再走一般的存活次数记录与升级策略
2 尝试放老生代,老生代Gc,可能是serial或G1,可能清理后老生代连续空间小于100M,当然新生代也还有空间;
也可能老生代GC策略是CMS,存在浮动垃圾且空间不连续,所以放不下;



Eden区域的继续划分
Thread Local Allocation Buffer TLAB
jvm为每个线程分配的私有缓存区域,否则,多线程同时分配内存是,为避免操作同一地址,可能需要加锁等级制,进而影响共享速度。
如下图所示,start和end就是起止地址,top表示已经分配到哪里了。分配新对象,top移动。top和end相遇,表示缓存已满,会再分配。



友情链接: