垃圾收集器介绍和ParNew&CMS与底层三色标记算法详解---必看

1.垃圾收集算法

垃圾收集器介绍和ParNew&CMS与底层三色标记算法详解---必看_第1张图片

 

现在的垃圾回收分带理论其实已经始于几十年前了,java的回收算法和垃圾回收器一直都在改进从未出现一款能适合所有场景的回收机制。所以我们进行垃圾回收的核心思想是 结合具体业务框架技术等客观条件合理选择垃圾回收器并分配资源。

现在这个机制:

一般将java 堆分为新生代和老年代 在新生代中的一般是我们java中朝生夕死的对象每次垃圾回收都会有大约99%的对象死去。现在一般采用复制算法。

垃圾收集器介绍和ParNew&CMS与底层三色标记算法详解---必看_第2张图片

老年代的对象存活比例较高。这里推荐大家先了解什么对象存入新生代,什么对象存入老年代。什么对象存入栈中。如何你把上面的整个理解了80%以上 那么就可以继续读下去了。对于老年代我们应该采用标记清除或者标记整理。应该根据自己数据特点选择是否需要整理。以及老年代做几次GC整理一次。

1.标记清除算法

垃圾收集器介绍和ParNew&CMS与底层三色标记算法详解---必看_第3张图片

主要分为两个阶段标记 和清除阶段 可以正着标记也可以反着标记 在标记完成后统一回收。但是存在两个问题:

1.效率问题。

2.空间内会产生大量内存碎片。(数组和某些大对象需要连续的空间存放。)

2.标记整理:

垃圾收集器介绍和ParNew&CMS与底层三色标记算法详解---必看_第4张图片

就是清除后统一进行整理,虽然这里的地址会被改变,但是利用计算机底层的一些屏障机制等

可以改变我们的引用地址。

周志明:

针对老年代对象的存亡特征,1974Edward Lueders提出了另外一种有针对性的标记-

Mark-Compact)算法,其中的标记过程仍然与标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存,

标记-复制算法在对象存活率较高时就要进行较多的复制操作,效率将会降低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法。

标记-清除算法与标记-整理算法的本质差异在于前者是一种非移动式的回收算法,而后者是移动式的。是否移动回收后的存活对象是一项优缺点并存的风险决策:

如果移动存活对象,尤其是在老年代这种每次回收都有大量对象存活区域,移动存活对象并更新

所有引用这些对象的地方将会是一种极为负重的操作,而且这种对象移动操作必须全程暂停用户应用程序才能进行[1],这就更加让使用者不得不小心翼翼地权衡其弊端了,像这样的停顿被最初的虚拟机设计者形象地描述为“Stop The World

但如果跟标记-清除算法那样完全不考虑移动和整理存活对象的话,弥散于堆中的存活对象导致的

空间碎片化问题就只能依赖更为复杂的内存分配器和内存访问器来解决。譬如通过分区空闲分配链

来解决内存分配问题(计算机硬盘存储大文件就不要求物理连续的磁盘空间,能够在碎片化的硬盘

 

上存储和访问就是通过硬盘分区表实现的)。内存的访问是用户程序最频繁的操作,甚至都没有之

一,假如在这个环节上增加了额外的负担,势必会直接影响应用程序的吞吐量。

基于以上两点,是否移动对象都存在弊端,移动则内存回收时会更复杂,不移动则内存分配时会

更复杂。从垃圾收集的停顿时间来看,不移动对象停顿时间会更短,甚至可以不需要停顿,但是从整个程序的吞吐量来看,移动对象会更划算。此语境中,吞吐量的实质是赋值器(Mutator,可以理解为使用垃圾收集的用户程序,本书为便于理解,多数地方用“用户程序用户线程代替)与收集器的效率总和。即使不移动对象会使得收集器的效率提升一些,但因内存分配和访问相比垃圾收集频率要高得多,这部分的耗时增加,总吞吐量仍然是下降的。HotSpot虚拟机里面关注吞吐量的ParallelScavenge收集器是基于标记-整理算法的,而关注延迟的CM S收集器则是基于标记-清除算法的,这也从侧面印证这点。

另外,还有一种和稀泥式解决方案可以不在内存分配和访问上增加太大额外负担,做法是让虚

拟机平时多数时间都采用标记-清除算法,暂时容忍内存碎片的存在,直到内存空间的碎片化程度已经大到影响对象分配时,再采用标记-整理算法收集一次,以获得规整的内存空间。前面提到的基于标记-清除算法的CMS收集器面临空间碎片过多时采用的就是这种处理办法。

[1] 最新的ZGCShenandoah收集器使用读屏障(Read Barrier)技术实现了整理过程与用户线程的并发执行

[2] 通常标记-清除算法也是需要停顿用户线程来标记、清理可回收对象的,只是停顿时间相对而言要来的短而已。

     如果你看完周说的并理解了恭喜你,如果没看懂 上面的大致意思就是 标记整理 会浪费更多的资源。我们根据自己本身业务的硬件与程序数据的结构自己选择合适的,也可以用混用来和稀泥。CMS就是这样的。就JDK1.8吧来说如果你应用程序在3--16G内存空间的 那么CMS无疑是优异的。如果小于3G 那么采用更简单的垃圾收集器或许是不错的选择。

常用的垃圾收集器:

垃圾收集器介绍和ParNew&CMS与底层三色标记算法详解---必看_第5张图片

一下这几个回收器摘用补充

作者:公众号_Zack说码
链接:https://juejin.im/post/5bade237e51d450ea401fd71
来源:掘金

CSDN:https://blog.csdn.net/maoyeqiu/article/details/97316982

Serial收集器

Serial,是单线程执行垃圾回收的。当需要执行垃圾回收时,程序会暂停一切手上的工作,然后单线程执行垃圾回收。

因为新生代的特点是对象存活率低,所以收集算法用的是复制算法,把新生代存活对象复制到老年代,复制的内容不多,性能较好。

垃圾收集器介绍和ParNew&CMS与底层三色标记算法详解---必看_第6张图片

 

单线程地好处就是减少上下文切换,减少系统资源的开销。但这种方式的缺点也很明显,在GC的过程中,会暂停程序的执行。若GC不是频繁发生,这或许是一个不错的选择,否则将会影响程序的执行性能。 对于新生代来说,区域比较小,停顿时间短,所以比较使用。

Serial Old收集器

老年代的收集器,与Serial一样是单线程,不同的是算法用的是标记-整理(Mark-Compact)。

垃圾收集器介绍和ParNew&CMS与底层三色标记算法详解---必看_第7张图片

 

因为老年代里面对象的存活率高,如果依旧是用复制算法,需要复制的内容较多,性能较差。并且在极端情况下,当存活为100%时,没有办法用复制算法。所以需要用Mark-Compact,以有效地避免这些问题。

 

Parallel Scavenge收集器(JDK8默认收集器)

新生代的收集器,同样用的是复制算法,也是并行多线程收集。与ParNew最大的不同,它关注的是垃圾回收的吞吐量。

这里的吞吐量指的是 总时间与垃圾回收时间的比例。这个比例越高,证明垃圾回收占整个程序运行的比例越小。

Parallel Scavenge收集器提供两个参数控制垃圾回收的执行:

  • -XX:MaxGCPauseMillis,最大垃圾回收停顿时间。这个参数的原理是空间换时间,收集器会控制新生代的区域大小,从而尽可能保证回收少于这个最大停顿时间。简单的说就是回收的区域越小,那么耗费的时间也越小。
    所以这个参数并不是设置得越小越好。设太小的话,新生代空间会太小,从而更频繁的触发GC。
  • -XX:GCTimeRatio,垃圾回收时间与总时间占比。这个是吞吐量的倒数,原理和MaxGCPauseMillis相同。

因为Parallel Scavenge收集器关注的是吞吐量,所以当设置好以上参数的时候,同时不想设置各个区域大小(新生代,老年代等)。可以开启**-XX:UseAdaptiveSizePolicy**参数,让JVM监控收集的性能,动态调整这些区域大小参数。

Parallel Old收集器(JDK8默认收集器)

老年代的收集器,是Parallel Scavenge老年代的版本。其中的算法替换成Mark-Compact。

垃圾收集器介绍和ParNew&CMS与底层三色标记算法详解---必看_第8张图片

 

适用场合:在注重CPU资源和吞吐量的情况下Parallel Scavenge 和 Parallel Old会是不错的选择。但是parNEw和CMS的组合STW感觉更轻微用户体现会更好。

ParNew收集器

ParNew同样用于新生代,是Serial的多线程版本,并且在参数、算法(同样是复制算法)上也完全和Serial相同。

Par是Parallel的缩写,但它的并行仅仅指的是收集多线程并行,并不是收集和原程序可以并行进行。ParNew也是需要暂停程序一切的工作,然后多线程执行垃圾回收。

垃圾收集器介绍和ParNew&CMS与底层三色标记算法详解---必看_第9张图片

 

因为是多线程执行,所以在多CPU下,ParNew效果通常会比Serial好。但如果是单CPU则会因为线程的切换,性能反而更差。

CMS收集器

是一款以获取最短回收时间为目的的收集器,非常注重用户体验

整个过程分为四步:

  • 初始标记(initial mark),单线程执行,需要“Stop The World”,但仅仅把GC Roots的直接关联可达的对象给标记一下,由于直接关联对象比较小,所以这里的速度非常快。
  • 并发标记(concurrent mark),对于初始标记过程所标记的初始标记对象,进行并发追踪标记,此时其他线程仍可以继续工作。此处时间较长,但不停顿。(这里是垃圾回收中耗时最长的且会产生漏标)
  • 重新标记(remark),在并发标记的过程中,由于可能还会产生新的垃圾,所以此时需要重新标记新产生的垃圾。此处执行并行标记,与用户线程不并发,所以依然是“Stop The World”,时间比初始时间要长一点。
  • 并发清除(concurrent sweep),并发清除之前所标记的垃圾。其他用户线程仍可以工作,不需要停顿。
  • 并发重置 重置GC本次标记的数据  (和清除一样 可能会漏 不过下次回收就解决了。)

垃圾收集器介绍和ParNew&CMS与底层三色标记算法详解---必看_第10张图片

主要优点:并发收集低停顿。

缺点:

对CPU资源敏感(会和服务抢资源)

会产生浮动垃圾 需要在下次GC时清理

Mark Sweep算法会导致内存碎片比较多(可以通过:-XXUseCMSCompactAtFullCollection参数 让JVM在清理垃圾后进行整理)

执行中的不确定行 如何并发收集垃圾时内存不够了 会采用Serial Old收集器 并STW 专心收集垃圾 这个过程很漫长 一定要避免

CMS的相关核心参数

1. -XXx:+UseConcMarkSweepGC:启用cms

2. -XX:ConcGCThreads:并发的GC线程数

3. -XX:+UseCMSCompactAtFullCollection: FullGC之 后做压缩整理(减少碎片)

4. -XX:CMSFullGCsBeforeCompaction:多少次FullGC之后压缩- -次。默认是0,代表每次FullGC后都会压缩一

5. -XX:CMSInitiatingOccupancyFraction:当老年代使用达到该比例时会触发FulIGC (默认是92,这是百分比)6. -XXx:+UseCMSInitiatingOccupancyOnly:只使用设定的回收阈值(-XX:CMSInitiatingOccupancyFraction设 定的值),如果不指定,JVM仅在第- -次使用设定值, 后续则会自动调整

7. -XX:+ CMSScavengeBeforeRemark:在CMS GC前启动- -次minor gc,目的在于减少老年代对年轻代的引 用,降低CMS GC的标记阶段时的开销,一般CMS的GC耗时 80%都在标记阶段

8. -XX:+ CMSParallellnitialMarkEnabled:表示在初始标记的时候多线程执行,缩短STW

9. -XX:+ CMSParallelRemarkEnabled:在重新标记的时候多线程执行,缩短STW;

10.ParNew&CMS -XX:UseParNewGC -XXx:+UseConcMarkSweepGC

三色算法:

在并发标记的过程中,因为标记期间应用线程还在继续跑,对象间的引用可能发生变化,多标和漏标的情况就有可能发生。 这里我们引入“三色标记"来给大家解释下,把Gcroots可达性分析遍历对象过程中遇到的对象,按照“是否访问过”这个条件标记成以 下三种颜色:

●黑色:表示对象已经被垃圾收集器访问过,且这个对象的所有引用都已经扫描过。 黑色的对象代表已经扫描 过,它是安全存活的,如果有其他对象引用指向了黑色对象,无须重新扫描一 遍。黑色对象不可能直接 (不经过

灰色对象)指向某个白色对象。

灰色:表示对象已经被垃圾收集器访问过,但这个对象上至少存在一个引用还没有被扫描过。

白色:表示对象尚未被垃圾收集器访问过。显然在可达性分析刚刚开始的阶段,所有的对象都是白色的, 若 在分析结束的阶段,仍然是白色的对象,即代表不可达。

垃圾收集器介绍和ParNew&CMS与底层三色标记算法详解---必看_第11张图片

看懂了上图 我们来看周的:

垃圾收集器介绍和ParNew&CMS与底层三色标记算法详解---必看_第12张图片

 

读写屏障:

解决并发扫描时的对象消失问题,只需破坏这两个条件的任意一个即可。

两种解决方案:增量更新(Incremental Update)和原始快照(Snapshot At The BeginningSAT B

增量更新就是当黑色对象插入新的指向白色对象的引用关系时,就讲这个新插入的引用记录下来等并发扫描结束后再讲这些记录的的引用关系中黑色的根重新扫描一次,即黑色对象一旦新插入了指向白色对象的引用之后,他就变成灰色对象了。

原始快照急速当黑色对象要删除指向白色对象的引用时,就将这个要删除的引用记录下来,在并发扫描结束之后再将这些记录的引用中的灰色对象为根重新扫描一次,这样就能扫描到白色对象并将白色对象直接标记为黑色对象。

无论是对引用关系的插入还是删除,虚拟机的记录操作都是通过写屏障实现的。

写屏障 推荐参考https://blog.csdn.net/qq_21383435/article/details/106311542

 

 

 

你可能感兴趣的:(JVM与垃圾调优专栏)