JVM之经典的垃圾收集器(基于《深入理解Java虚拟机》之第三章垃圾收集器与内存分配策略)(中)

sad 基于上一篇的GC回收机制分析,丹丹学妹非要给我说一下她所了解的经典GC器,让我们拭目以待吧~


sadwqewqeqweqwe若说GC算法是内存回收的方法论,那GC器就是内存回收的实践者


经典垃圾收集器组合

JDK7~JDK9期间,OracleJDK中的HotSpot虚拟机所包含的全部可用的垃圾收集器之间的关系如图所示:
在这里插入图片描述

JDK9以后,HotSpot虚拟机所包含的全部可用的垃圾收集器之间的关系如图所示(具体原因后面分析):
在这里插入图片描述

  • 该图中展示了七种作用于不同分代的收集器:
    wqeqweqwe新生代收集器:wqwe①Serial收集器wqeqweqwe②Parallel Scavenge收集器wqewe③ ParNew收集器
    wqeqweqwe老年代收集器:wqwe④Serial Old收集器wqeqwe⑤CMS收集器wqeqsadsaddweqwe⑥Parallel Old 收集器
    wq qweqwe 整堆收集器:wdswse⑦ G1收集器 wq qwasdasdasdase ⑤和⑦是重点。

在分别介绍这其中不同的GC器前,我们先回顾几个概念:

  • ①、Minor GC: 又称 新生代GC,指发生在新生代的GC动作;因为Java对象大多是朝生夕灭,所以Minor GC非常频繁,一般回收速度也比较快;
  • ②、Major GC: 又称 Full GC老年代GC,指发生在老年代的GC; 出现Major GC经常会伴随至少一次的Minor GC(不是绝对,Parallel Sacvenge收集器就可以选择设置Major GC策略);Major GC速度一般比Minor GC慢10倍以上; 因为Java对象大多是朝生夕灭,所以Minor GC非常频繁,一般回收速度也比较快;
  • ③、并行: 指多条垃圾收集线程并行工作,而此时用户线程仍然处于等待状态;
    wq qweqwe如 ParNew、Parallel Scavenge、Parallel Old、G1;
  • ④、并发: 指用户线程与垃圾收集线程同时/交替执行;
    wq qweqwe如CMS、G1(也有并行);
    我们接下来分别介绍这七种不同分代的GC器,注意: 没有最好的GC器,只有适合的GC器。

Serial收集器

  • Serial收集器是 最基础历史最悠久的收集器;
  • 特点:
    wes⒈新生代;
    wse⒉采用”标记-复制“算法;
    wse⒊单线程工作的收集器;它的“单线程”的意义有两方面:
    wsadasdseⅠ、使用一个处理器或一条GC线程去完成GC工作;
    wsadasdseⅡ、在GC时,必须暂停其他所有工作线程(STW),直到它GC结束;
  • 缺点: STW的时间长;
  • 优点:
    we⒈简单而高效(与其他收集器的单线程相比);
    we⒉额外内存消耗最小 (在内存资源受限的环境适用,比如近年流行的微服务应用中);
    we⒊单线程GC效率高(因为没有线程交互的开销,只专心做GC,适用单核处理器或者核心数较小的多核处理器);
  • 参数 -XX:+UseSerialGC: 添加该参数来显式的使用串行垃圾收集器;
  • 应用场景:HotSpot虚拟机运行在 客户端 模式下的默认新生代收集器;

Serial Old收集器

  • Serial Old是Serial GC器的老年代版本;
  • 特点:
    wes⒈老年代;
    wes⒉采用标记-整理算法;
    wes⒊单线程收集;
  • 应用场景:
    wes主要用于主要用于 客户端模式;
    wes如果在服务端模式下,它也可能有两种用途:
    weasdasds Ⅰ、JDK5以前,Serial Old ➕ Parallel Scavenge联合使用;
    wasdasdes Ⅱ、作为CMS收集器发生失败时的后备预案,在并发收集发生 Concurrent Mode Failure时使用;

asdasdsaasdasdsadasadadsadsad Serial➕Serial Old收集器运行示意图:
在这里插入图片描述


ParNew收集器

  • ParNew收集器是Serial收集器的 多线程并行版本;
    【注】:收集算法、Stop The World、对象分配规则、回收策略等都与Serial收集器完全一致;**
  • 应用场景:在 Server模式下,ParNew收集器是一个非常重要的收集器,因为除Serial外,目前只有它能与CMS收集器配合工作;但在单个CPU环境中,不会比Serail收集器有更好的效果,因为存在线程交互开销。
    【注】:CMS是HotSpot在JDK1.5推出的第一款真正意义上的并发(Concurrent)收集器,第一次实现了让垃圾收集线程与用户线程(基本上)同时工作;CMS作为老年代收集器,但却无法与JDK1.4已经存在的新生代收集器Parallel Scavenge配合工作;因为Parallel Scavenge以及G1都没有使用传统的GC收集器代码框架,而另外独立实现;而其余几种收集器则共用了部分的框架代码;
  • 参数:
    wes⒈-XX:+UseConcMarkSweepGC: 指定使用CMS后,会默认使用ParNew作为新生代收集器;
    wes⒉-XX:+UseParNewGC: 强制指定使用ParNew;
    wes⒊-XX:ParallelGCThreads: 指定垃圾收集的线程数量,ParNew默认开启的收集线程与CPU的数量相同;
    asdasdsaasdasdsadasadadsadsadParNew➕Serial Old(JDK9废除)组合收集器运行示意图如下:
    在这里插入图片描述

Parallel Scavenge收集器

  • 特点:
    wes新生代收集器;
    wes⒉基于 标记-复制算法实现的收集器;
    wes并行收集的多线程收集器;
    wes⒋ 它的关注点与其他收集器不同,CMS等收集器的关注点是尽可能地缩短GC时用户线程的停顿时间,而 Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量(Throughput),因此也被称作被称作“ 吞吐量优先收集器”。
    【注】:吞吐量就是处理器用于运行用户代码的时间与处理器总消耗时间的比值;
  • 参数:
    wes⒈-XX:MaxGCPauseMillis: 控制最大GC停顿时间,范围是大于0的毫秒数;
    wses注】:该参数不能无限小,因为垃圾收集停顿时间缩短是以牺牲吞吐量和新生代空间为代价换取的;
    wes⒉-XX:GCTimeRatio: 直接设置吞吐量大小的,范围大于0小于100的整数;
    wes⒊-XX:+UseAdaptiveSizePolicy: 这个参数被激活之后,就不需要人工指定新生代的大小、Eden与Survivor区域的比例、晋升老年代对象大小等细节参数了,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量;
  • 应用场景:以高吞吐量为目标,即减少垃圾收集时间,让用户代码获得更长的运行时间;当应用程序运行在具有多个CPU上,对暂停时间没有特别高的要求时,即程序主要在后台进行计算,而不需要与用户进行太多交互,比如批处理。

Parallel Old收集器

  • Parallel Old是Parallel Scavenge收集器的 老年代版本,JDK 6时才开始提供;
  • 特点:
    wes老年代收集器;
    wes ⒉支持 多线程并发收集;
    wes ⒊基于 标记-整理算法实现;
  • 应用场景:JDK1.6及之后用来代替老年代的Serial Old收集器,特别是在Server模式,多CPU的情况下, 这样在注重吞吐量以及CPU资源敏感的场景,就有了Parallel Scavenge➕Parallel Old收集器的"给力"应用组合
  • 参数-XX:+UseParallelOldGC: 指定使用Parallel Old收集器
    asdasdsaasdasdsadasadadsadsad Parallel Scavenge➕Parallel Old收集器运行示意图如下
    在这里插入图片描述

CMS收集器

  • CMS(Concurrent Mark Sweep)收集器是一种 以获取最短GC停顿时间为目标的收集器
  • 特点:
    wes老年代GC;
    wes ⒉ 基于" 标记-清除" 算法;
    wes ⒊ 是HotSpot在JDK1.5推出的第一款 真正意义上的并发(Concurrent)收集器,第一次实现了让 GC线程与用户线程(基本上)同时工作
  • 应用场景:目前很大一部分的Java应用集中 在互联网网站或者基于浏览器的B/S系统的服务端上,这类应用通常都会较为关注服务的响应速度,希望系统停顿时间尽可能短,以给用户带来良好的交互体验。
  • 参数-XX:+UseConcMarkSweepGC: 指定使用CMS收集器;
  • CMS收集器是基于标记-清除算法实现的,但是整个过程却不太一样,整体分为四个步骤:

sdasdsasdasdasdsadsadsadasd①、初始标记 ②、并发标记 ③、重新标记 ④、并发清除

【注】: ① 和 ③ 两个步骤需要"STW"
ssaasd⒈初始标记: 仅只是 标记一下GC Roots能直接关联到的对象
ssaasd⒉并发标记: 从GC Roots的直接关联对象也就是①的基础上,开始 遍历整个对象图的过程,耗时长,但不需要停顿用户线程;
ssaasd⒊重新标记: 修正了并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,①<停顿时间<<③
ssaasd⒋并发清除: 清理标记阶段判断的已经死亡的对象,由于不需要移动存活对象,所以这个阶段也是可以与用户线程同时 并发的。
ssaasd总结: 由于在整个过程中耗时最长的 并发标记和并发清除阶段中,垃圾收集器线程都可以与用户线程一起工作,所以从总体上来说,CMS收集器的GC回收过程是与用户线程一起并发执行的
asdasdsaasdasdsadasdsadasadadsadsad CMS收集器运行示意图如下:
在这里插入图片描述

  • 优点:并发收集、低停顿、也称之为“并发低停顿收集器”;
  • 缺点:
    ssaasd⒈CMS收集器对处理器资源非常敏感(毕竟是面向并发设计的收集器)。在并发阶段,它虽然不会导致用户线程停顿,但却会因为占用了一部分线程(或者说处理器的计算能力)而导致应用程序变慢,降低总吞吐量;
    ssasdas[注]:CMS默认GC线程数量是(CPU数量+3)/4,当不足4个,性能不是很好;
    ssaasd⒉CMS收集器无法处理“浮动垃圾”,甚至出现有可能出“Con-current Mode Failure”失败进而导致另一次完全“StopThe World”的 Full GC的产生。
    ssasds【浮动垃圾】: 在CMS的并发标记和并发清理阶段,用户线程是还在继续运行的,程序在运行自然就还会伴随有新的垃圾对象不断产生,但这一部分垃圾对象是出现在标记过程结束以后,CMS无法在当次收集中处理掉它们,只好留待下一次垃圾收集时再清理掉。这一部分垃圾就称为“浮动垃圾”。
    ssaasd⒊基于“标记-清除”算法实现的收集器统一的缺点,也就是说着收集结束时会有 大量空间碎片产生。空间碎片过多时,将会给大对象分配带来很大麻烦,往往会出现老年代还有很多剩余空间,但就是无法找到足够大的连续空间来分配当前对象,而不得不提前触发一次 Full GC的情况。
  • 并发收集带来的问题: 由于在垃圾收集阶段用户线程还需要持续运行,那就还需要预留足够内存空间提供给用户线程使用,因此CMS收集器不能像其他收集器那样等待到老年代几乎完全被填满了再进行收集,必须预留一部分空间供并发收集时的程序运作使用(JDK5,CMS收集器当老年代使用了68%的空间后就会被激活,JDK6时该参数是92%)
  • 参数-XX:CMSInitiatingOccu-pancyFraction: 可以适当调高参数的值来提高CMS的触发百分比,降低内存回收频率,获取更好的性能
    【触发百分比过高带来的影响】: 要是CMS运行期间预留的内存无法满足程序分配新对象的需要,就会出现一次“并发失败”(Concurrent Mode Failure),这时候虚拟机将不得不启动后备预案:冻结用户线程的执行,临时启用Serial Old收集器来重新进行老年代的垃圾收集,但这样停顿时间就很长了,所以这触发百分比需要权衡设计不能太高也不能过低。

Garbage First(G1)收集器

  • G1是一款面向 服务端应用的GC器,主要针对 配备多核CPU及大容量内存的机器,以极高概率满足GC停顿时间的同时,还兼顾高吞吐量的性能

  • 特点:
    ssaasd⒈并行与并发:
    ssasddsaasd①、并行性: G1在回收期间,可以有多个Gc线程同时工作,有效利用多核计算能力。此时用户线程STW
    ssasddsaasd②、并发性: G1拥有与应用程序交替执行的能力 ,部分工作可以和应用程序同时执行,一般来说,不会在整个回收阶段发生完全阻塞应用程序的情况
    ssaasd⒉分代收集:
    ssasddsaasd①、 能独立管理整个GC堆(新生代和老年代),而不需要与其他GC搭配;
    ssasddsaasd②、能够 采用不同方式处理不同区域的对象,将整个堆划分为多个大小相等的独立区域 (Region),这些区域中包含了逻辑上的年轻代和老年代,它们不再要求一定是物理上连续的;
    ssaasd⒊结合多种垃圾收集算法: 比如 空间整合不产生空间碎片从整体看,是基于"标记-整理"算法;从局部看,Region之间是是基于"标记-复制"算法;这是一种类似火车算法的实现;都不会产生内存碎片,有利于长时间运行;
    ssaasd⒋可预测的停顿时间模型: G1除了追求低停顿处,还能建立可预测的停顿时间模型(这样低停顿的同时实现高吞吐量),可以明确指定M毫秒时间片内,GC消耗的时间不超过N毫秒;
    【注】:JDK9以后,G1 是HotSpot的默认垃圾收集器,取代了CMS 回收器。
    在这里插入图片描述
    【上图分析】:
    ssddsaasd①、所有的Region大小相同,且在JVM生命周期内不会被改变。 每个Region块大小根据堆空间的实际大小而定,整体被控制在1MB到32MB之间,且为2的N次幂,即1MB, 2MB, 4MB, 8MB, 1 6MB, 32MB。
    ssddsaasd②、一个region 有可能属于Eden, Survivor 或者0ld/Tenured 内存区域。但是一个region只可能属于一个角色。图中的E表示该region属于Eden内存区域,s表示属于Survivor内存区域,0表示属于0ld内存区域。图中空白的表示未使用的内存空间。
    ssddsaasd③、增加了一种新的内存区域,叫做Humongous内存区域, 如图中的H块。主要用于存储大对象,如果超过1.5个region,就放到H中。(对于堆中的大对象,默认直接会被分配到老年代,但是如果它是一个短期存在的大对象,就会对垃圾收集器造成负面影响。为了解决这个问题 ,G1划分了一个Humongous区,它用来专门存放大对象。如果一个H区装不下一个大对象,那么G1会寻找连续的H区来存储。为了能找到连续的H区,有时候不得不启动Full GC。G1的大多数行为都把H区作为老年代的一部分来看待

  • 应用场景:
    ssaasd⒈面向 服务端应用,针对具有 大内存、多处理器的机器;
    ssaasd⒉ 为 需要低GC延迟,并具有大堆的应用程序提供解决方案;

  • G1比CMS性能好的情况:
    ssaasd⒈超过50%的Java堆被活动数据占用;
    ssaasd⒉对象分配频率或年代提升频率变化很大;
    ssaasd⒊GC停顿时间过长(长于0.5至1秒);
    ssd【注】:G1无论是为了垃圾收集产生的内存占用(Footprint)还是程序运行时的额外执行负载(Overload)都要比CMS要高。
    ssaad内存占用方面: 是因为G1的卡表实现更为复杂,而且堆中每个Region,无论扮演的是新生代还是老年代角色,都必须有一份卡表,这导致G1的记忆集(和其他内存消耗)可能会占整个堆容量的20%乃至更多的内存空间。
    ssaad执行负载方面: 虽然它们都会用到写屏障,但CMS只是用写后屏障来更新维护卡表,而G1除了使用写后屏障来进行同样的(由于G1的卡表结构复杂,其实是更烦琐的)卡表维护操作外,为了实现原始快照搜索(SATB)算法,还需要使用写前屏障来跟踪并发时的指针变化情况。相比起增量更新算法,原始快照搜索能够减少并发标记和重新标记阶段的消耗,避免CMS那样在整个标记阶段停顿时间过长的缺点,但是在用户程序运行过程中确实会产生由跟踪引用变化带来的额外负担。由于G1对写屏障的复杂操作要比CMS消耗更多的运算资源,所以CMS的写屏障实现是直接的同步操作,而G1就不得不将其实现为类似于消息队列的结构,把写前屏障和写后屏障中要做的事情都放到队列里,然后再异步处理。

  • 参数:
    ssaasd⒈-XX:+UseG1GC: 指定使用G1收集器;
    ssaasd⒉-XX:InitiatingHeapOccupancyPercent: 当整个Java堆的占用率达到参数值时,开始并发标记阶段;默认为45;
    ssaasd⒊-XX:MaxGCPauseMillis: 为G1设置暂停时间目标,默认值为200毫秒
    ssaassd【注】: 如果这个值设置很小,如20ms,那么它收集的region会少,这样长时间后,堆内存会满。产生FullGC,FullGC会出现STW,反而影响用户体验;
    ssaasd⒋-XX:G1HeapRegionSize: 设置每个Region的大小。值是2的幂,范围是1MB到32MB之间,目标是根据最小的Java堆大小划分出约2048个区域。默认是堆内存的1/2000;
    ssaasd⒌ -XX:ParallelGCThread: 设置STW时GC线程数的值。最多设置为8GC线程);
    ssaasd⒍-XX:ConcGCThreads: 设置并发标记的线程数。将n设置为并行垃圾回收线程数(ParallelGCThreads)的1/4左右;

  • 调优操作步骤:
    ssaasd⒈开启G1垃圾收集器
    ssaasd⒉设置堆的最大内存
    ssaasd⒊设置最大的停顿时间

  • 在并发标记阶段如何保证收集线程与用户线程互不干扰地运行?
    答:CMS收集器采用增量更新算法实现,而G1收集器则是通过原始快照(SATB)算法来实现的。此外垃圾收集对用户线程的影响还体现在回收过程中新创建对象的内存分配上,程序要继续运行就肯定会持续有新对象被创建,G1为每一个Region设计了两个名为TAMS(Top at Mark Start)的指针并发回收时新分配的对象地址都必须要在这两个指针位置以上, G1收集器默认在这个地址以上的对象是被隐式标记过的,即默认它们是存活的,不纳入回收范围。
    【注】:与CMS中的“Concurrent Mode Failure”失败会导致Full GC类似,如果内存回收的速度赶不上内存分配的速度,G1收集器也要被迫冻结用户线程执行,导致Full GC而产生长时间“Stop The World”。

  • 为什么G1收集器可以实现可预测的停顿?如何做到可靠的呢?
    答:G1可以建立可预测的停顿时间模型是因为它可以有 是因为它将Region作为单次回收的最小单元,即每次收集到的内存空间都是Region大小的整数倍,这样可以有计划地避免在整个Java堆中进行全区域的垃圾收集。 更具体地处理思路是G1跟踪各个Region获得其收集价值大小,在后台维护一个优先列表; 每次根据允许的收集时间,优先回收价值最大的Region(名称Garbage-First的由来),这就保证了在有限的时间内可以获取尽可能高的收集效率
    可靠的解释: G1收集器的停顿预测模型是以衰减均值(Decaying Average)为理论基础来实现的,在垃圾收集过程中,G1收集器会记录每个Region的回收耗时、每个Region记忆集里的脏卡数量等各个可测量的步骤花费的成本,并分析得出平均值、标准偏差、置信度等统计信息。这里强调的“衰减平均值”是指它会比普通的平均值更容易受到新数据的影响,平均值代表整体平均状态,但衰减平均值更准确地代表“最近的”平均状态。换句话说,Region的统计状态越新越能决定其回收的价值。然后通过这些信息预测现在开始回收的话,由哪些Region组成回收集才可以在不超过期望停顿时间的约束下获得最高的收益。

  • 跨Region引用对象的问题?(记忆集,写屏障)
    答:无论G1还是其他分代收集器,JVM都是使用Remembered Set来避免全局扫描, 每个Region都有一个Remembered Set;每次Reference类型数据写操作时,都会产生一个Write Barrier暂时中断操作; 然后检查将要写入的引用指向的对象是否和该Reference类型数据在不同的Region(其他收集器:检查老年代对象是否引用了新生代对象); 如果不同,通过CardTable把相关引用信息记录到引用指向对象的所在Region对应的Remembered Set中当进行垃圾收集时,在GC根节点的枚举范围加入Remembered Set;就可以保证不进行全局扫描,也不会有遗漏。
    在这里插入图片描述
    【另一种说法】:使用记忆集避免全堆作为GC Roots扫描,但在G1收集器上记忆集的应用其实要复杂很多,它的每个Region都维护有自己的记忆集这些记忆集会记录下别的Region指向自己的指针,并标记这些指针分别在哪些卡页的范围之内。G1的记忆集在存储结构的本质上是一种哈希表,Key是别的Region的起始地址,Value是一个集合,里面存储的元素是卡表的索引号(这种“双向”的卡表结构(卡表是“我指向谁”,这种结构还记录了“谁指向我”)),这种结构比原来的卡表实现起来更复杂,同时由于Region数量比传统收集器的分代数量明显要多得多,因此G1收集器要比其他的传统垃圾收集器有着更高的内存占用负担。根据经验,G1至少要耗费大约相当于Java堆容量10%至20%的额外内存来维持收集器工作。

  • G1收集器运作过程(不计算维护Remembered Set的操作,可以分为4个步骤(与CMS较为相似):
    ssaasd⒈初始标记: 仅只是 标记一下GC Roots能直接关联到的对象,且 修改TAMS(Next Top at Mark Start)来保证下一阶段并发运行时,用户程序能在正确可用的Region中创建新对象(需要"Stop The World",但耗时很短);
    ssaasd⒉并发标记: 从GC Roots的直接关联对象也就是①的基础上,开始 遍历整个对象图的过程,耗时长,但不需要停顿用户线程(和CMS一样),当对象图扫描完成以后,还要重新处理SATB记录下的在并发时有引用变动的对象);
    ssaasd⒊最终标记: 对用户线程做另一个短暂的暂停,用于处理并发阶段结束后仍遗留下来的最后那少量的SATB记录。( 需要"Stop The World",且停顿时间比初始标记稍长,但远比并发标记短;采用多线程并行执行来提升效率);
    ssaasd⒋筛选回收: 负责更新Region的统计数据,对各个Region的回收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个Region构成回收集,然后把决定回收的那一部分Region的存活对象复制到空的Region中,再清理掉整个旧Region的全部空间。这里的操作涉及存活对象的移动,是必须 暂停用户线程,由多条收集器线程 并行 完成的。
    【筛选回收为什么没有设计成与用户程序一起执行并发执行?】: 其实也考虑让其一起并发执行,但是这种操作比较复杂,并且我们回收的不是所有的Region,而是一部分,因此停顿时间是可控的;还有就是G1不仅仅是面对低时延,还需要保证吞吐量,而停顿用户线程能最大程度的提高GC效率,因此我们把这个操作放到了ZGC中实现。
    ssaasd【注】: G1收集器除了并发标记外,其余阶段也是要完全暂停用户线程;
    asdasdsaasdasdsadasdsadasadadsadsad G1收集器运行示意图如下:
    在这里插入图片描述

  • 意义:
    ⒈G1收集器是垃圾收集器技术发展历史上的里程碑式的成果,开创了收集器面向 局部收集的设计思路基于Region的内存布局形式,并在JDK8提供了 并发的类卸载的支持,也称为“ 全功能的垃圾收集器”;
    【为什么说是里程碑】:从G1开始,最先进的垃圾收集器的设计导向都不约而同地变为追求能够应付应用的内存分配速率,而不追求一次把整个Java堆全部清理干净。这样,应用在分配,同时收集器在收集,只要收集的速度能跟得上对象分配的速度,那一切就能运作得很完美.
    ⒉JDK 9版本,G1宣告取代Parallel Scavenge➕Parallel Old组合,成为 服务端模式下的默认垃圾收集器,而 CMS则沦落至被声明为不推荐使用(Deprecate)的收集器
    ⒊JDK 10版本,HotSpot虚拟机提出了“ 统一垃圾收集器接口”,将内存回收的“行为”与“实现”进行分离,CMS以及其他收集器都重构成基于这套接口的一种实现。 以此为基础,日后要移除或者加入某一款收集器,都会变得容易许多。


别人博客看到的一个G1垃圾回收过程:

G1回收器的回收过程(另一角度)

  • G1 的GC过程主要包括三个环节:
    sadasdasdasdasd⒈年轻代GC(Young GC)
    sadasdasdasdasd⒉年轻代GC➕老年代并发标记过程(Concurrent Marking)
    sadasdasdasdasd⒊混合回收(Mixed GC)
    在这里插入图片描述
  • 什么时候开始年轻代GC?
    年轻代的Eden区快用尽时开始年轻代的GC过程,G1的年轻代收集阶段是一个 并行(多个GC线程)的独占式收集器组成。在年轻代回收期,G1 GC 暂停所有应用程序线程,启动多线程执行年轻代回收。然后 从年轻代区间移动存活对象到Survivor区间或者老年区间, 也有可能是两个区间都会涉及。
  • 什么时候开始年轻代GC+老年代并发标记?
    堆内存使用达到一定值(默认45%)时, 开始老年代并发标记过程;
  • 什么时候开始混合回收?
    老年代标记完成马上开始混合回收过程。对于一个混合回收期,G1 GC从老年区间移动存活对象到空闲区间,这些空闲区间也就成为了老年代的一部分。 和年轻代不同,老年代的G1回收器和其他GC不同,G1的老年代回收器不需要整个老年代被回收 ,一次只需要扫描/回收一小部分老年代的Region就可以了。同时,这个 老年代Region是和年轻代一起被回收的

年轻代GC详解

  • 当Eden空间耗尽时,G1会启动一次年轻代垃圾回收过程,来回收 Eden区和 Survivor区:
    asdasdsaasdasdsadasadadsadsad 年轻代GC前后对比图如下

在这里插入图片描述
在这里插入图片描述

  • ①、根扫描: 一定要考虑 remembered Set,看是否有老年代中的对象引用了新生代对象;
    ②、更新RSet:处理dirty card queue中的card,更新RSet。 此阶段完成后,RSet可以准确的反映老年代对所在的内存分段中对象的引用
    【dirty card queue】:dirty card queue: 对于应用程序的引用赋值语句object.field=object,JVM会在之前和之后执行特殊的操作以在dirty card queue中入队一个保存了对象引用信息的card。在年轻代回收的时候,G1会对Dirty CardQueue中所有的card进行处理,以更新RSet,保证RSet实时准确的反映引用关系。那为什么不在引用赋值语句处直接更新RSet呢?这是为了性能的需要,RSet的处理需要线程同步,开销会很大,使用队列性能会好很多;
    ③、处理RSet: 识别被老年代对象指向的Eden中的对象,这些被指向的Eden中的对象被认为是存活的对象;
    ④、复制对象:"标记-复制"算法,此阶段,对象图被遍历,Eden区内存段中存活的对象会被复制到Survivor区中空的内存分段Survivor区内存段中存活的对象如果年龄未达阈值,年龄会加1达到阀值会被会被复制到old区中空的内存分段。如果Survivor空间不够,Eden空间的部分数据会直接晋升到老年代空间;
    ⑤、处理引用: 处理Soft,Weak, Phantom, Final, JNI Weak等引用。最终Eden空间的数据为空,GC停止工作,而 目标内存中的对象都是连续存储的,没有碎片,所以复制过程可以达到内存整理的效果,减少碎片

并发标记过程详解

  • ①. 初始标记阶段:标记从根节点直接可达的对象。这个阶段是STW的,并且会触发一次年轻代GC;
    ②.根区域扫描:G1 GC扫描Survivor区直接可达的老年代区域对象,并标记被引用的对象。这一过程必须在young GC之前完成(YoungGC时,因为会动Survivor区,所以这一过程必须在young GC之前完成)
    ③.并发标记 : 在整个堆中进行并发标记(和应用程序并发执行),此过程可能被young GC中断。在并发标记阶段,若发现区域对象中的所有对象都是垃圾,那这个区域会被立即回收。同时,并发标记过程中,会计算每个区域的对象活性(区域中存活对象的比例)。
    ④.再次标记: 由于应用程序持续进行,需要修正上一次的标记结果。是STW的。G1中采用了比CMS更快的原始快照算法:snapshot一at一the一beginning (SATB);
    ⑤.独占清理: 计算各个区域的存活对象和GC回收比例,并进行排序,识别可以混合回收的区域。为下阶段做铺垫。是STW的。(这个阶段并不会实际上去做垃圾的收集)
    ⑥.并发清理阶段: 识别并清理完全空闲的区域

混合回收

  • Mixed GC并不是FullGC,老年代的堆占有率达到参数(-XX:InitiatingHeapOccupancyPercent)设定的值则触发,回收所有的Young和部分Old(根据期望的GC停顿时间确定old区垃圾收集的优先顺序)以及大对象区, 正常情况G1的垃圾收集是先做MixedGC,主要使用复制算法,需要把各个region中存活的对象拷贝到别的region里去,拷贝过程中如果发现没有足够的空region能够承载拷贝对象就会触发一次Full GC(网图,如下)
    在这里插入图片描述

Full GC

  • 堆内存过小,当G1在复制存活对象的时候没有空的内存分段可用,则会回退到full gc,这种情况可以通过增大内存解决;
  • 暂停时间-XX:MaxGCPauseMillis设置短,回收频繁。由于用户线程和GC线程一起执行,可能用户线程产生的垃圾大于GC线程回收的垃圾,会导致内存不足,触发Full gc;

7种经典垃圾回收器总结

在这里插入图片描述

怎么选择GC器?

  • Java垃圾收集器的配置对于JVM优化来说是-一个很重要的选择,选择合适的垃圾收集器可以让JVM的性能有一个很大的提升。
  • 怎么选择垃圾收集器?
    sdada①、优先调整堆的大小让JVM自适应完成;
    sdada②、如果 内存小于100M,使用串行GC器;
    sdada③、如果是单核、单机程序,并且没有停顿时间的要求,选择串行GC器;
    sdada④、如果是 多CPU、需要高吞吐量、 允许停顿时间超过1秒,选择并行或者JVM自己选择;
    sdada⑤、如果是 多CPU、追求低停顿时间,需快速响应(比如延迟不能超过1秒,如互联网应用),使用并发收集器官方推荐G1,性能高。现在互联网的项目,基本都是使用G1。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值