垃圾回收器和原理(CMS、G1)

垃圾收集算法

  1. 标记清除(会有内存碎片,大对象时更有可能因为没有足够的连续内存而触发GC,但是可以降低了STW的时间)

  2. 复制算法(优点是没有碎片,缺点是浪费空间和复制过程中会STW,需要有专门的空白的内存块来存储复制过来的对象,而且在复制的过程中需要STW。年轻代适合复制算法,默认牺牲1/10的空间:Survivor to区),适合大部分都是垃圾的业务场景,年轻代。

  3. 标记整理(或者也叫标记压缩,避免了内存碎片,同时也避免了复制算法的内存空间浪费,但是压缩的时候涉及到内存地址的移动,此期间还是会STW),适合大部分都不是垃圾的业务场景,老年代

  4. 分代回收:老年代和年轻代

其它细节

  1. 并发指的是同时发生,比如一边GC回收,一边程序运行,用户行为不会受到影响。并行指的是一个相同的操作同时多个线程来执行,比如ParNew多个线程同时进行YGC。

  2. 垃圾收集器的搭配是有限定的,不是任意都可以搭配。

垃圾回收器

年轻代回收器:Serial、Parallel Scanvenge、ParNew
老年代回收器:Serial Old、Parallel Old、CMS
综合回收器:G1

  1. Serial & Serial_Old 收集器
  • 分为年轻代Serial(复制算法) 和老年代的 Serial Old(标记整理算法)
  • 串行回收,高效 稳定但是stop the world较长
  • 适合单核CPU,效率高而且稳定,避免了线程切换的开销
  • 是Client模式下的默认GC机制
  • Serial Old是CMS PromotionFailure后的默认替代回收器
  1. ParNew回收器:CMS的最佳伴侣,因为CMS只能和Serial和ParNew配合使用,而ParNew是多线程版本的Serial

  2. Parallel Scavenge回收器:多线程,追求高吞吐量(运行代码时间/运行代码时间+GC时间) 和参数自动调整

  • 支持配置GCMaxPause和GCTimeRatio两个参数,来控制GC最大停顿时间和吞吐量,默认99%
  • 支持配置UseAdaptiveSizePolicy,自动调整年轻代 Eden、S0、S1区的大小和晋升老年代ratio,从而来满足上面的两个参数
  • 不能和CMS一起搭配使用,而和SerialOld搭配又浪费,所以一般都是和Parallel Old搭配使用
  1. Parallel Old:标记整理,吞吐量优先,追请的是性能最大化,比如一些后台计算类,报表生成类的应用

CMS收集器

适合多CPU,频繁和用户交互的业务场景,追求的是停顿时间最短,采用标记清除的回收算法(因为标记清除不会涉及存活对象的移动:复制和压缩,从而降低STW的时间)

回收4个阶段

  1. 初始标记:STW,快速获取到老年代中的GC Roots对象,以及遍历新生代中GC Roots对象引用到老年代中的对象, 默认是单线程执行,也可以设置多线程数并行进行。
  2. 并发标记:并发客户线程,只分配一个线程来根据初始标记里面的对象开始,逐个标记 初始标记成功的对象的引用的对象。如果并发期间,对象的引用关系发生改变,则会被标记为dirty对象。
  3. 重新标记:STW,修正并发标记期间,引用关系的改变,这个过程比初始标记稍长。
  4. 并发清除:专门分配一个线程来清除标记过的对象,不影响用户操作,此阶段会有新的浮动垃圾生成,如果太多,会造成PromotionFailure

CMS的缺点

  1. 对CPU资源敏感,因为在并发标记、并发清除的时候都需要占用一个单独的线程来完成操作,如果CPU为2的话,那么就只有1个CPU能干活,一个CPU在GC
  2. 无法处理浮动垃圾,也就是每次GC都扫不干净,因为并发清除的过程中,还会有新的垃圾产生,而且如果这个阶段新的垃圾导致空间不足,会PromotionFailure
  3. 基于标记清除算法会存在很多碎片,空间利用率不高,需要配置压缩参数,但是在压缩的过程中STW的时间会延长

G1回收器

吞吐量和最短停顿时间本来就互相矛盾,Parallel Old追求的是吞吐量,CMS追求的是STW的最短,而G1通过把堆分成多个相对独立的Region块,并行的进行选择性的回收,实现一个两者兼顾的回收器。

  1. G1类似于CMS,G1回收器支持用户并发操作,MajorGC在并发标记时允许用户线程同时进行业务操作。

  2. G1相对CMS而言,更加灵活,因为内部是多个Rigion组成,而且每个Rigion是独立的(Rigion之间对象的相互引用记录在Remembered Set里面),所以可以根据用户设定的允许最长停顿时间,在一次MajorGC的过程中只回收部分Rigion(回收空间价值最高而经验推算的时间成本又最低的Rigion块,比如Rigion1对象活跃度10%,Rigion2对象活跃度50%,那么优先回收Rigion1块),这是CMS无法做到的,CMS的老年代是完整的一块内存,要GC的话,就必须整个老年代全部GC,而整个老年代全部GC的话,停顿的时间就会相对更长。年轻代来看,也更加灵活,因为年轻代的Eden区和S区的Rigion块数量是动态调整的。

  3. G1相对CMS而言,因为采用的是复制算法,所以没有内存碎片的产生。避免了堆中大对象因为没有足够的连续的内存空间而触发MajorGC的情况。其实CMS之所以使用标记清除算法,是为了减少压缩垃圾过程时的STW,因为G1可以做到部分而不是所有Rigion块的GC,所以可以在预测的时间内完成复制操作。

  4. G1的适合大内存的JVM环境。因为采用复制的算法(YGC和MajorGC都是复制),所以需要有一定的空闲内存块来存储复制来的对象,空间利用率低。而且每次GC都不是完整的GC,需要该Region块的活跃度低于一定标准,这个Region块才会发起回收,所以一些活跃度高的内存块里面的垃圾对象不会被回收掉,默认堆内存使用率达到45%的时候,就开始FullGC。

  5. G1适合多CPU的JVM环境。因为传统的回收器都是3个内存块,而G1默认是2000多个相对独立的内存块,在GC几乎所有的过程中,Region块都是高度并行化的进行回收,而且还有在并发标记阶段支持用户并发性的业务操作,所以对CPU的要求较高

  6. G1的灵活性还在于能够根据设置的STW时间,动态调整各个分代的内存占比,有点类似于Parallel Old,但是和CMS是不同的。

G1回收器FullGC回收过程

  1. 初始标记,STW,分为老年代和年轻代两部分。先会触发一次YGC(排除掉年轻代对老年代有引用,但是该年轻代对象已经死亡的情况),然后再对Survivor to区进行遍历(确保所有的年轻代的对象都是活的,因为经历了一次YGC),查找并标记S区对象对老年代的直接引用的所有老年代对象(这里是直接引用,比如Survivor to区A对象,引用了老年代的B对象,就标记B对象,虽然B对象还可能引用了C、D、E对象)。第二部分是针对老年代,标记老年代中所有GC Roots直接引用的对象。
  2. 并发标记,根据可达性分析,找到所有GCRoots的对象引用,一层一层的标记,这个时候没有STW,用户线程正常执行。
  3. 最终标记,STW,修正并发标记期间的对象关系变更,根据Remembered Set Logs,来修改Remember Set,该阶段可以多个线程并行
  4. 筛选回收,STW,会跟踪各个Rigion里面的垃圾堆积的价值大小(比如需要回收的对象的空间大小以及回收所需时间的经验值)排序,根据用户设定的停顿时间,回收特定Region,因为每个Region块都有独立的Remembered Set,所以每个Region可以独立的进行回收,也就是多个Region块并行多线程的垃圾回收。

G1回收的细节

  1. 当执行垃圾回收时,G1以类似于CMS回收器的方式运行。G1执行一个并发的全局标记阶段,以确定整个堆中对象的活跃度。
  2. G1使用一个暂停预测模型来满足用户定义的暂停时间目标并基于指定的暂停时间目标选择回收的区域数量
  3. G1因为经常性的不完整GC,活跃度高的内存块GC的会更晚,而且需要有空闲的Region来实现复制,进行垃圾回收的时机更早,默认在堆内存使用率达到45%时就会开始垃圾回收。这体现了G1(Garbage-First/垃圾回收优先)这一名字的含义。至于活跃度多低才会进行回收,则是由G1决定的,G1会调整自己的回收策略来尽可能满足用户设置的最大STW时长。
  4. 每个Rigion都有一个Remembered Set,来纪录Region块之间的对象引用,RSet使得区域并行独立的回收成为可能。当程序发生写操作的时候,会判断相关的引用是否涉及到跨Region的引用,比如Rigion0当中,A对象之前是引用Rigion0中的B对象,现在执行程序,A对象被重新赋值,引用了Rigion1中的C对象,那么在Rigion1的Remembered Set当中就要纪录,否则Region1在GC的时候,会把C对象直接回收了,通过读取自己的Remembered Set知道C对象被Region0种的A对象引用,这样既避免了全堆的扫描,又实现了分块管理。
  5. FullGC触发的时机是内存使用率达到一定的比例,默认45%

G1中的年轻代,可以说明一下几点:

  • YGC触发的条件是Eden区满了
  • Survivor To块可以认为是一个空白块,因为YGC和MajorGC都是采用复制算法,都是复制到一个空白的内存块当中。
  • 年轻代垃圾回收使用多线程并行进行,需要STW
  • 年轻代内存由一组非连续的区域组成,而且Eden、S区的大小是动态调整的
  • 存活对象被拷贝到新的幸存区或者年老代区域(超过存活ratio的对象进入老年代)

参考资料

https://www.jianshu.com/p/3cddf780c050

你可能感兴趣的:(垃圾回收器和原理(CMS、G1))