CMS和G1的回收过程

https://www.jianshu.com/p/0a5b26691b77

https://blog.csdn.net/u011546953/article/details/78994882

我们先回顾一下主流Java的垃圾回收器(HotSpot JVM)。本文是针对堆的垃圾回收展开讨论的。

堆被分解为较小的三个部分。具体分为:新生代、老年代、持久代。

 

image

 

  1. 绝大部分新生成的对象都放在Eden区,当Eden区将满,JVM会因申请不到内存,而触发Young GC ,进行Eden区+有对象的Survivor区(设为S0区)垃圾回收,把存活的对象用复制算法拷贝到一个空的Survivor(S1)中,此时Eden区被清空,另外一个Survivor S0也为空。下次触发Young GC回收Eden+S0,将存活对象拷贝到S1中。新生代垃圾回收简单、粗暴、高效。
  2. 若发现Survivor区满了,则将这些对象拷贝到old区或者Survivor没满但某些对象足够Old,也拷贝到Old区(每次Young GC都会使Survivor区存活对象值+1,直到阈值)。 3.Old区也会进行垃圾收集(Young GC),发生一次 Major GC 至少伴随一次Young GC,一般比Young GC慢十倍以上。
  3. JVM在Old区申请不到内存,会进行Full GC。Old区使用一般采用Concurrent-Mark–Sweep策略回收内存。

总结:Java垃圾回收器是一种“自适应的、分代的、停止—复制、标记-清扫”式的垃圾回收器。

缺点:

  1. GC过程中会出现STW(Stop-The-World),若Old区对象太多,STW耗费大量时间。
  2. CMS收集器对CPU资源很敏感。
  3. CMS收集器无法处理浮动垃圾,可能出现“Concurrent Mode Failure”失败而导致另一次Full GC的产生。
  4. CMS导致内存碎片问题。

G1收集器

在G1中,堆被划分成 许多个连续的区域(region)。每个区域大小相等,在1M~32M之间。JVM最多支持2000个区域,可推算G1能支持的最大内存为2000*32M=62.5G。区域(region)的大小在JVM初始化的时候决定,也可以用-XX:G1HeapReginSize设置。

在G1中没有物理上的Yong(Eden/Survivor)/Old Generation,它们是逻辑的,使用一些非连续的区域(Region)组成的。

新生代收集

G1的新生代收集跟ParNew类似,当新生代占用达到一定比例的时候,开始出发收集。

 

image

 

 

image

 

被圈起的绿色部分为新生代的区域(region),经过Young GC后存活的对象被复制到一个或者多个区域空闲中,这些被填充的区域将是新的新生代;当新生代对象的年龄(逃逸过一次Young GC年龄增加1)已经达到某个阈值(ParNew默认15),被复制到老年代的区域中。

回收过程是停顿的(STW,Stop-The-Word);回收完成之后根据Young GC的统计信息调整Eden和Survivor的大小,有助于合理利用内存,提高回收效率。

回收的过程多个回收线程并发收集。

老年代收集

和CMS类似,G1收集器收集老年代对象会有短暂停顿。

  1. 标记阶段,首先初始标记(Initial-Mark),这个阶段是停顿的(Stop the World Event),并且会触发一次普通Mintor GC。对应GC log:GC pause (young) (inital-mark)
  2. Root Region Scanning,程序运行过程中会回收survivor区(存活到老年代),这一过程必须在young GC之前完成。
  3. Concurrent Marking,在整个堆中进行并发标记(和应用程序并发执行),此过程可能被young GC中断。在并发标记阶段,若发现区域对象中的所有对象都是垃圾,那个这个区域会被立即回收(图中打X)。同时,并发标记过程中,会计算每个区域的对象活性(区域中存活对象的比例)。

    image

  4. Remark, 再标记,会有短暂停顿(STW)。再标记阶段是用来收集 并发标记阶段 产生新的垃圾(并发阶段和应用程序一同运行);G1中采用了比CMS更快的初始快照算法:snapshot-at-the-beginning (SATB)。
  5. Copy/Clean up,多线程清除失活对象,会有STW。G1将回收区域的存活对象拷贝到新区域,清除Remember Sets,并发清空回收区域并把它返回到空闲区域链表中。

    image

  6. 复制/清除过程后。回收区域的活性对象已经被集中回收到深蓝色和深绿色区域。

 

image

 

关于Remembered Set概念:G1收集器中,Region之间的对象引用以及其他收集器中的新生代和老年代之间的对象引用是使用Remembered Set来避免扫描全堆。G1中每个Region都有一个与之对应的Remembered Set,虚拟机发现程序对Reference类型数据进行写操作时,会产生一个Write Barrier暂时中断写操作,检查Reference引用的对象是否处于不同的Region之间(在分代中例子中就是检查是否老年代中的对象引用了新生代的对象),如果是便通过CardTable把相关引用信息记录到被引用对象所属的Region的Remembered Set中。当内存回收时,在GC根节点的枚举范围加入Remembered Set即可保证不对全局堆扫描也不会有遗漏。

G1虽然保留了CMS关于代的概念,但是代已经不是物理上连续区域,而是一个逻辑的概念。在标记过程中,每个区域的对象活性都被计算,在回收时候,就可以根据用户设置的停顿时间,选择活性较低的区域收集,这样既能保证垃圾回收,又能保证停顿时间,而且也不会降低太多的吞吐量。Remark阶段新算法的运用,以及收集过程中的压缩,都弥补了CMS不足。引用Oracle官网的一句话:“G1 is planned as the long term replacement for the Concurrent Mark-Sweep Collector (CMS)”。

参考来源

 

说明:本文摘自《深入理解Java虚拟机》,是自己看书总结文章。以下正文开始

收集器中的***并行(Parallel)***语义:指多条垃圾收集线程并行工作,但此时用户线程仍处于等待状态

收集器中的***并发(Concurrent)***语义:指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),用户程序在继续运行,而垃圾收集程序于另一个CPU上。

###CMS收集器:
CMS(ConCurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,从Mark-Sweep上可以看出,CMS收集器(以下简称CMS)是基于“标记-清除”算法实现的。主要应用于B/S模式的服务端(希望系统停顿时间尽可能短,尤其重视响应时间)
CMS的运作过程可以分为4个步骤

**1. 初始标记(CMS initial mark) **

**2. 并发标记(CMS concurrent mark) **

3. 重新标记(CMS remark)

4. 并发清除(CMS concurrent sweep)

  1. 详细说明:初始标记(CMS initial Mark)是需要“Stop The World”。初始标记仅仅只是标记一下GC Roots能关联到的对象,速度很快。

  2. 并发标记阶段就是进行GC Roots Tracing 的过程。

  3. 重新标记阶段:因为并发标记阶段用户程序继续执行,导致原先标记产生变动,所以需要对原先标记的记录进行修正。从这个作用可以看出,重新标记阶段和初始标记阶段同样需要“Stop The World”。这个阶段的停顿时间一般比初始标记阶段稍长一点,但远比并发标记的时间要短。

  4. 并发清除阶段:从前面的语义解释就可以看出,该阶段用户线程同垃圾清除线程同时执行(这里可以看出由于清除阶段用户线程还在运行, 自然就会产生新的垃圾(称为“浮动垃圾”), 因为新产生的垃圾在垃圾标记阶段之后,所以这部分新产生的垃圾CMS无法在本次收集过程中处理掉它们,只能留到下次GC时再清理)。
    这里写图片描述
    ####CMS收集器的缺点

    1. 对CPU资源很敏感:CMS默认启动的回收线程数量是(CPU的个数+3)/4,如果CPU的个数在4个以上,
      收集器会占用不少于25%的CPU资源,CPU个数越多,收集器会占用不少于25%的CPU资源,
      CPU个数越多,CPU资源占比越小。但是当CPU不足4(例如2个)的时候,CMS收集器占用一半的运算能力去执行收集器线程。本来CPU负载就比较大,就会导致用户程序的执行效率下降的很明显。

    2. 无法处理浮动垃圾:由于最后的垃圾清除阶段是并发进行的,伴随着程序的运行产生的新的垃圾,在本次收集过程中无法处理掉,指的下次GC时再清理。
      而且还需要留足够的内存空间给用户线程使用,不能像其他收集器那样等到老年代几乎被填满了再进行收集,需要预留一部分空间提供并发收集时用户线程使用。

    3. 产生大量的空间碎片。CMS是基于“标记-清除”算法实现的收集器。因为这种算法在收集结束后有大量的空间碎片,当碎片过多时,会给大对象的内存
      分配带来很大麻烦,往往在老年代中还有很大空间剩余,但是无法找到足够大的连续空间来分配当前对象导致不得不提前触发一次Full GC。

###G1(Garbage First)收集器
面向服务端应用的垃圾收集器。G1名称由来:G1收集器,Java堆的内存布局是将整个Java堆分为多个大小相等的独立区域(Region),也保留了新生代
和老年代的概念。但是新生代和老年代不再是物理隔离的,它们都是一部分Region的集合。G1跟踪各个Region里面的垃圾堆积的价值大小(也就是回收获得的空间大小以及回收需要的时间的经验值),在后台维护一个优先列表,每次根据允许的的收集时间,优先回收价值最大的Region。

G1的运作过程大致划分为几个步骤

1. 初始标记(Initial Marking)

2. 并发标记(Concurrent Marking)

3. 最终标记(Final Marking)

4. 筛选回收(Live Data Counting and Evacuation)

  1. 详细说明第一步:初始标记(Initial Marking),同CMS收集器很相似,仅仅是标记一下GC Root能关联到的对象,这阶段需要停顿线程,但耗时很短。

  2. 并发标记(Concurrent Marking):是从GC Root开始对堆中对象进行可达性分析,找出存活的对象,这阶段耗时比较长,但是可与用户程序并发执行。

  3. 最终标记(Final Marking):为了修正在并发标记期间因为用户程序执行而导致标记产生变动的那一部分的标记记录。所以这个阶段同初始标记阶段一样需要"Stop The World"。可以并行执行(即最终标记线程同时执行)。

  4. 筛选回收(Live Data Counting Evacuation):首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划。这个阶段也是"Stop The World"的。(这个阶段也可以做到同用户线程一起并发执行,因为只回收一部分Region时间是用户可控制的,停顿用户线程可以大幅提高收集效率。所以选择了“Stop The World”)。
    这里写图片描述
    ####G1收集器的特点:

    1. 并行与并发:G1能充分利用多CPU,多核的硬件优势来缩短Stop—The—World停顿的时间,部分其它收集器原本需要停顿用户线程执行的GC动作,
      G1依然可以通过并发的方式让用户线程继续执行。

    2. 分代收集:同其它收集器一样,分代概念依然在G1中保留。G1可以不需要其它收集器配合就能独立管理整个GC堆,而且采用了不同的方式处理
      新创建的对象,已经存活一段时间的对象,熬过多次GC的旧对象,来获得更好的收集结果。

    3. 空间整合:使用算法从整体上来看是基于标记——整理实现的。局部来看,是基于“复制”算法实现的。所以G1在运作期间不会产生内存空间碎片,
      收集后能提供规整的可用内存。这有利于程序长时间运行。

    4. 可预测的停顿:G1同CMS都追求低停顿时间。但是G1还能建立可预测的停顿时间模型(通过有计划的避免在整个Java堆中进行全区域的垃圾收集,
      而是将整个Java堆分为多个大小相等的独立区域(Region),也保留了新生代和老年代的概念。但是新生代和老年代不再是物理隔离的,它们都是
      优先列表,每次根据允许的的收集时间,优先回收价值最大的Region。),能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集
      的时间上不超过N毫秒,这几乎是实时Java的垃圾收集器的特征。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值