JVM垃圾收集器与HotSpot的算法细节

目录

HotSpot的算法细节实现

根节点枚举

安全点

安全区域

记忆集与卡表

记忆集

作用

卡表(Card Table)

卡页(Card Page)

元素变脏(Dirty)

写屏障

写屏障

写前屏障(Pre-Write Barrier)

写后屏障(Post-Write Barrier)

伪共享

伪共享解决方案

并发的可达性分析

为什么必须在一个能保障一致性的快照上才能进行对象图的遍历?

以下两个条件同时满足时,会产生“对象消失”的问题:(即原本应该是黑色的对象被误标为白色)

对象消失问题解决方案:

经典垃圾收集器

HotSpot虚拟机经典垃圾收集器的关系

并行与并发

并行

并发

Serial收集器

 特点

优点

缺点

使用方式

ParNew收集器

特点

优点

缺点

使用方式

JVM参数设置

Parallel Scavenge收集器

特点

吞吐量

 优点

缺点

适用场景

JVM参数设置

Serial Old收集器

特点

优点

缺点

应用场景

JVM参数设置

注意事项

Parallel Old收集器

特点

优点

缺点

应用场景

JVM参数设置

CMS收集器

特点

原理步骤

 优点

缺点

G1收集器(Garbage First收集器)

特点

Region中的特殊区域

Region区域

G1 GC过程

G1常用参数


HotSpot的算法细节实现

根节点枚举

        所有收集器在根节点枚举这一步骤时都是必须暂停用户线程的,因此毫无疑问根节点枚举与之前提及的整理内存碎片一样会面临相似的“Stop The World”的困扰。现在可达性分析算法耗时最长的查找引用连的过程已经可以做到与用户线程一起并发,但根节点枚举始终还是必须子一个能保障一致性的快照中才得到以进行——这里“一致性”的意思是整个枚举期间执行子系统看起来就像被冻结在某个时间点上,不会出现分析过程中,根节点集合的对象引用关系还在不断变化的情况,若这点不能满足的话,分析结果准确性也就无法保证。这是导致垃圾收集过程必须停顿所有用户线程的其中一个重要原因,即使是号称停顿时间可控,或者(几乎)不会发生停顿的CMS、G1、ZGC等收集器,枚举根节点时也是必须要停顿的。

        由于目前主流Java虚拟机使用的都是准确式垃圾收集,所以当用户线程停顿下来之后,其实并不需要一个不漏地检查完所有执行上下文和全局的引用位置,虚拟机应当是有办法直接得到哪些地方存放着对象引用的。

        在HotSpot的解决方案里,是使用一组称为OopMap的数据结构来达到这个目的。一旦类加载动作完成的时候,HotSpot就会把对象内什么偏移量上是什么类型的数据计算出来,在即时编译过程中,也会在特定的位置记录下栈里和寄存器里哪些位置是引用。这样收集器在扫描时就可以直接得知这些信息了,并不需要真正一个不漏地从方法区等GC Roots开始查找。

安全点

        在OopMap的协助下,HotSpot可以快速准确地完成GC Roots枚举,但一个很现实的问题随之而来:可能导致引用关系变化,或者说导致OopMap内容变化的指令非常多,如果为每一条指令都生成对应的OopMap,那将会需要大量的额外存储空间,这样垃圾收集伴随而来的空间成本就会变得无法忍受的高昂。

        实际上HotSpot也的确没有为每条指令都生成OopMap,只是在“特定的位置”记录了这些信息,这些位置被称为安全点(Sagepoint)。

        安全的设定,决定了用户程序执行时并非在代码指令流的任意位置都能够停顿下来开始垃圾收集,而是强制要求必须执行到达安全点后才能够暂停。因此,安全点的选定即不能太少以至于让收集器等待时间过长,也不能太过频繁以至于过分增大运行时间的内存负荷。

        安全点位置的选取基本上是以“是否具有让程序长时间执行的特征”为标准进行选定的,因为每条指令只能怪的时间都非常短暂,程序不太可能因为指令流长度太长这样的原因而长时间执行,“长时间执行”的最明显特征就是指令序列的复用,例如方法调用、循环跳转、异常跳转等都属于指令序列复用,所以只有具有这些功能的指令才会产生安全点。

安全区域

        使用安全点的设计似乎已经完美解决如何停顿用户线程,让虚拟机进入垃圾回收状态的问题了,但实际情况却并不一定。安全点机制保证了程序执行时,在不太长时间内就会遇到可进入垃圾收集过程的安全点。

        程序“不执行”的时候呢? 所谓的程序不执行就是没有分配处理器时间,典型的场景便是用户线程处于Sleep状态或者Blocked状态,这时候线程无法响应虚拟机的中断请求,不能再走到安全的地方去中断挂起自己,迅即也显然不可能持续等待线程重新被激活分配处理器时间。对于这种情况,就必须引入安全区域(Safe Region)来解决了。

        安全区域是指能够确保在某一段代码之中,引用关系不会发生变化,因此,在这个区域中任意地方开始垃圾收集都是安全的。可以把安全区域看作被扩展拉伸了的安全点

        当用户线程执行到安全区域里面的代码时,首先会标识自己已经进入了安全区域,那样当这段时间里虚拟机要发起垃圾收集时就不必去管这些已声明自己在安全区域内的线程了。当线程要离开安全区域时,它要检查虚拟机是否已经完成了根节点枚举(或者垃圾收集过程中其他需要暂停用户线程的阶段),如果完成了,那线程就当作没事发生过,继续执行;否则它就必须一直等待,知道收到可以离开安全区域的信号为止。

记忆集与卡表

记忆集

        在分代收集理论的时候,提到为解决对象跨代引用所带来的问题,垃圾收集器在新生代中建立了名为记忆集(Remembered Set)的数据结构。

        记忆集是一种用于记录从非收集区域指向收集区域的指针集合的抽象数据结构。抽象的意思是只定义了记忆集的行为意图,并没有定义其行为的具体实现。

作用

        用以避免把整个老年代加进GC Roots扫描范围。

        事实上并不只是新生代、老年代之间才有跨代引用的问题,所有涉及部分区域收集(Partial GC)行为的垃圾收集器,典型的如G1、ZGC和Shenandoah收集器,都会面临相同的问题.

        记录精度:

  • 字长精度:每个记录精确到一个机器字长(就是处理器的寻址位数,如常见的32位或64位,整个精度决定了机器访问物理内存地址的指针长度),该字包含跨代指针。
  • 对象精度:每个记录精确到一个对象,该对象里有字段含有跨代指针。
  • 卡精度:每个记录精确到一块内存区域,该区域内有对象含有跨代指针。

卡精度:指的是用一种称为“卡表”(Card Table)的方式去实现记忆集,也是目前最常用的一种记忆集实现形式。

记忆集是一种“抽象”的数据结构,抽象的意思是只定义了记忆集的行为意图,并没有定义其行为的集体实现。

卡表(Card Table)

        是记忆集的一种具体实现,定义了记忆集的记录精度、与堆内存的映射关系等。

卡页(Card Page)

        字节数组CARD_TABLE的每一个元素都对应着其标识的内存区域中一块特定大小的内存块。

元素变脏(Dirty)

        一个卡页的内存中通常包含不止一个对象,只要卡页内有一个(或更多)对象的字段存在着跨代指针,那就将对应卡表的数组元素的值标识为1。没有则标识为0.

写屏障

如何变脏,如果在对象赋值的那一刻去更新维护卡表呢?

        在HotSpot虚拟机里是通过写屏障(Write Barrier)技术维护卡表状态的。

写屏障

        可以看作在虚拟机层面对“引用类型字段赋值”整个动作的AOP切面,在引用对象赋值时会产生一个环形(Around)通知,供程序执行额外的动作,也就是说赋值的前后都在写屏障的覆盖范畴内。

写前屏障(Pre-Write Barrier)

        在赋值前的部分的写屏障叫作写前屏障。

写后屏障(Post-Write Barrier)

        在赋值后的部分的写屏障叫作写后屏障。

HotSpot虚拟机的许多收集器中都有使用到写屏障,但直至G1收集器出现之前,其他收集器都只用到了写后屏障。

卡表在高并发场景下还面临着“伪共享”问题。

伪共享

        是处理并发底层细节时一种经常需要考虑的问题,现代中央处理器的缓存系统中是以缓存行(Cache Line)为单位存储的,当多线程修改互相独立的变量时,如果这些变量恰好共享同一个缓存行,就会彼此影响(写回、无效化或者同步)而导致性能降低,这就是伪共享问题。

伪共享解决方案

        不采用无条件的写屏障,而是先检查卡表标记,只有当该卡表元素未被标记过时才将其标记为变脏。

        HotSpot虚拟机新参数:-XX:+UseCondCardMark,用来决定是否开启卡表更新的条件判断。开启会增加一次额外判断的开销,但能够避免伪共享问题,两者各有性能损耗,是否打开要根据应用实际运行情况来进行测试权衡。

并发的可达性分析

        可达性分析算法理论上要求全过程都基于一个能保障一致性的快照中才能够进行分析,这意味着必须全程冻结用户线程的运行。

        在根节点枚举这个步骤中,由于GC Roots相比起整个Java堆中全部的对象毕竟还算是极少数,且在各种优化技巧(OopMap)的加持下,它带来的停顿已经是非常短暂且相对固定(不随堆容量而增长)的了。可从GC Roots再继续往下遍历对象图,这一步骤的停顿时间就必定会与Java堆容量直接成正比例关系了:堆越大,存储的对象越多,对象图结构越复杂,要标记更多对象而产生的停顿时间自然就更长。

为什么必须在一个能保障一致性的快照上才能进行对象图的遍历?

        三色标记(Tri-color Marking)作为工具来辅助推导,把遍历对象图过程中遇到的对象,按照“是否访问过”这个条件标记成以下颜色:

  • 白色:表示对象尚未被垃圾收集器访问过。显然在可达性分析刚刚开始的阶段,所有的对象都是白色的,若在分析结束的阶段,仍然是白色的对象,即代表不可达。
  • 黑色:表示对象已经被垃圾收集器访问过,且这个对象的所有引用都已经扫描过。黑色的对象代表已经扫描过,它是安全存活的,如果有其他对象引用指向了黑色对象,无须重新扫描一遍。黑色对象不可能直接(不经过灰色对象)指向某个白色对象。
  • 灰色:表示对象已经被垃圾收集器访问过,但这个对象上至少存在一个引用还没有被扫描过。

JVM垃圾收集器与HotSpot的算法细节_第1张图片

JVM垃圾收集器与HotSpot的算法细节_第2张图片

以下两个条件同时满足时,会产生“对象消失”的问题:(即原本应该是黑色的对象被误标为白色)

  •  赋值器插入了一条或多条从黑色对象到白色对象的新引用;
  • 赋值器删除了全部从灰色对象到该白色对象的直接或间接引用。

对象消失问题解决方案:

        只需破坏这两个条件的任意一个即可。

  •  增量更新(Incremental Update)
    • 增量更新要破坏的是第一个条件,当黑色对象插入新的指向白色对象的引用关系时,就将这个新插入的引用记录下来,等并发扫描结束之后,再将这些记录过的引用关系中的黑色对象为根,重新扫描一次。这可以简化理解为,黑色对象一旦新插入了指向白色对象的引用之后,它就变回灰色对象了。
  • 原始快照(Snapshot At The Beginning)
    • 原始快照要破坏的时第二个条件,当灰色对象要删除指向白色对象的引用关系时,就将这个要删除的引用记录下来,在并发扫描结束之后,再将这些记录过的引用关系中的灰色对象为根,重新扫描一次。这也可以简化理解为,无论引用关系删除与否,都会按照刚刚开始扫描那一刻的对象图快照来进行搜索。

以上无论时对引用关系记录的插入还是删除,虚拟机的记录操作都是通过写屏障实现的。在HotSpot虚拟机中,增量更新和原始快照这两种解决方案都有实际应用。

譬如,CMS是基于增量更新来做并发标记的G1、Shenandoah则是用原始快照来实现

经典垃圾收集器

HotSpot虚拟机经典垃圾收集器的关系

JVM垃圾收集器与HotSpot的算法细节_第3张图片

图注:七种不同分代的收集器,如果两个收集器之间存在连线,就说明它们可以搭配使用;

           收集器所处的区域,则表示它是属于新生代收集器或者是老年代收集器。

并行与并发

并行

        并行(Parallel):并行描述的是多条垃圾收集器线程之间的关系,说明同一时间有多条这样的线程在协同工作,通常默认此时用户线程是处于等待状态。

并发

        并发(Concurrent):并发描述的是垃圾收集器线程与用户线程之间的关系,说明同一时间垃圾收集器线程与用户线程都在运行。由于用户线程并未被冻结,所以程序仍然能响应服务请求,但由于垃圾收集器线程占用了一部分系统资源,此时应用程序的处理的吞吐量将受到一定影响。

Serial收集器

 特点

  • 采用标记复制算法
  • 新生代收集器
  • 最基础、历史最悠久的收集器;
  • 在JDK1.3.1之前是HotSpot虚拟机新生代收集器的唯一选择;
  • 是一个单线程工作的收集器
    • 1.使用一个处理器或一条收集线程去完成垃圾收集工作;
    • 2.必须暂停其他所有工作线程,直到它收集结束。(“Stop The World”,由虚拟机在后台自动发起和自动完成的)

举个栗子:

        “你妈妈在给你打扫房间的时候,肯定也会让你老老实实地在椅子上或者房间外待着,如果她一边打扫,你一边乱扔纸屑,这房间还能打扫完?”

        所以就会把你限制住,暂停你一切行动,直到打扫完。哈哈哈哈,这栗子挺形象,画面感满满。

优点

  • 对于其他收集器的单线程相比:简单而高效;
  • 对于内存资源受限的环境相比:是所有收集器额外内存消耗最小的;
  • 对于单核处理器或处理器核心数较少的环境相比:Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集效率
  • 对于运行在客户端模式下的虚拟机是一个很好的选择。

缺点

  • Stop The World时间过长

使用方式

 -XX:+UseSerialGC

ParNew收集器

特点

  • 采用标记复制算法
  • 新生代收集器
  • 实质上是Serial收集器的多线程并行版本;
  • 其余行为包括Serial收集器可用的所有控制参数、收集算法、Stop The World、对象分配规则、回收策略与Serial收集器完全一致。

优点

  • 除了Serial收集器外,目前只有它能与CMS收集器配合工作
  • 在多CPU服务器上,效果比Serial好很多

缺点

  • 在单核心处理器的环境中不会比Serial收集器效果好

使用方式

-XX:+UseParNewGC

JVM参数设置

  • 设置线程数:XX:ParllGCThreads
  • 指定默认新生代收集器:激活CMS后,使用 -XX:UseConcMarkSweepGC
  • 强制指定或者禁用它:-XX:+/-UseParNewGC
  • 限制垃圾收集的线程数:-XX:ParallelGCThreads

Parallel Scavenge收集器

特点

  • 采用标记复制算法
  • 新生代收集器,又称吞吐量优先收集器
  • 与其他收集器的关注点不同,它是达到一个可控制的吞吐量(Throughput)。
  • 自适应调节策略,自动指定年轻代、Eden、Suvisor区的比例。

吞吐量

        是处理器用于运行用户代码的时间与处理器总消耗时间的比值。

JVM垃圾收集器与HotSpot的算法细节_第4张图片

 优点

  • 具有良好的吞吐量性能,适合于高并发、数据量大的应用程序;
  • 具有自适应调节策略,可以根据当前系统负载自动调节垃圾回收参数,以达到最优的垃圾回收效果

缺点

  • 可能会占用较多的CPU资源,影响应用程序的响应性能

适用场景

  • 后台运算
  • 交互不多的任务(批量处理、订单处理、科学计算等)

JVM参数设置

  • 控制最大垃圾收集停顿时间:-XX:MaxGCPauseMillis

-XX:MaxGCPauseMillis参数允许的值是一个大于0的毫秒数,收集器将尽力保证内存回收花费的时间不超过用户设定值。

        不要认为这个参数设置的更小一点就能使得系统的垃圾收集速度变得更快,垃圾收集停顿时间缩短是以牺牲吞吐量和新生代空间为代价换取的:系统把新生代调得小一些,收集300MB新生代肯定比收集500MB快,但这也直接导致垃圾收集发生得更频繁,原来10秒收集一次、每次停顿100毫秒,现在变成5秒收集一次、每次停顿70毫秒。停顿时间的确在下降,但吞吐量也降下来了。

  • 直接设置吞吐量大小:-XX:GCTImeRatio

-XX:GCTimeRatio参数的值则应当是一个大于0小于100的整数,也就是垃圾收集时间占总时间的比率,相当于吞吐量的倒数。

        譬如把此参数设置为19,那允许的最大垃圾收集时间就占总时间的5%(即1/(1+19)),默认值为99,即允许最大1%(即1/(1+99))的垃圾收集时间。

  • 开关参数,垃圾收集的自适应的调节策略:-XX:+UseAdaptiveSizePolicy

是一 个开关参数,当这个参数被激活之后,就不需要人工指定新生代的大小(-Xmn)、Eden与Survivor区 的比例(-XX:SurvivorRatio)、晋升老年代对象大小(-XX:PretenureSizeThreshold)等细节参数 了,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时 间或者最大的吞吐量。这种调节方式称为垃圾收集的自适应的调节策略(GC Ergonomics)

Serial Old收集器

特点

  • 采用标记-整理算法
  • 老年代收集器,单线程收集器
  • 供客户端模式下的HotSpot虚拟机使用
  • 在服务端模式下,两种用途:
    • 1.是在JDK 5以及之前的版本中与Parallel Scavenge收集器搭配使用
    • 2.是作为CMS收集器发生失败时的后备预案,在并发收集发生Concurrent Mode Failure时使用。

优点

        即以上特点。

缺点

       未知,(知道的猿友们可基于补充,评论即可)

应用场景

主要用于Client模式

  1. 在JDK1.5及之前,与Parallel Scavenge收集器搭配使用(JDK1.6有Parallel Old收集器可搭配);
  2. 作为 CMS收集器的后备预案 ,在并发收集发生Concurrent Mode Failure时使用

JVM参数设置

使用方式:-XX:+UseSerialGC

注意事项

需要说明一下, Parallel Scavenge收集器架构中本身有PS MarkSweep收集器来进行老年代收集, 并非 直接调用 Serial Old收集器, 但是这个PS MarkSweep收集器与Serial Old的实现几乎是一样的, 所以在官方的许多资料中都是 直接以Serial Old代替PS MarkSweep进行讲解.

Parallel Old收集器

特点

  • 采用标记-整理算法
  • 老年代收集器,多线程并发收集

优点

  • JDK1.6及之后用来代替老年代的Serial Old收集器;
  • 解决了Parallel Scavenge收集器的尴尬地位(老年代只有单线程收集器)
  • 在注重吞吐量或者处理器资源较为稀缺的场合,可用优先考虑Parallel Scavenge 加 Parallel Old 收集器

缺点

        未知,(知道的猿友们可基于补充,评论即可)

应用场景

  • JDK1.6及之后用来代替老年代的Serial Old收集器;
  • 特别是在Server模式,多CPU的情况下;
  • 这样在注重吞吐量以及CPU资源敏感的场景,就有了Parallel Scavenge加Parallel Old收集器的"给力"应用组合;

JVM参数设置

-XX:+UseParallelOldGC:指定使用Parallel Old收集器

CMS收集器

        CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的Java应用集中在互联网网站或者基于浏览器的B/S系统的服务端上,这类应用通常都会较为关注服务得响应速度,希望系统停顿时间尽可能短,以给用户带来良好得交互体验。CMS收集器就非常符合这类应用的需求。

特点

  • 采用标记-清除算法
  • 多线程老年代收集器
  • 以获取最短回收停顿时间为目标的收集器,称为“并发低停顿收集器”(Concurrent Low Pause Collector)

原理步骤

        1.初始标记(CMS initial mark)

初始标记仍然需要“Stop The World”.

初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快。

        2.并发标记(CMS concurrent mark)

并发标记阶段就是从GC Roots的直接关联对象开始遍历整个对象图的过程,这个过程耗时较长但是不需要停顿用户线程,可以与垃圾收集线程一起并发运行。

        3.重新标记(CMS remark)

重新标记阶段则是为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间通常会比初始标记阶段稍长一些,但也远比并发标记阶段的时间短。

        4.并发清除(CMS concurrent sweep)

并发清除阶段,清理删除掉标记阶段判断的已经死亡的对象,由于不需要移动存活对象,所以这个阶段也是可以与用户线程同时并发的。

JVM垃圾收集器与HotSpot的算法细节_第5张图片

 优点

  • 并发收集
  • 低停顿

缺点

  • 并发阶段占用线程,导致应用程序变慢,降低总吞吐量
  • 无法处理浮动垃圾(可能出现“Con-current Mode Failure”失败进而导致另一次完成“Stop The World”的Full GC的产生)
  • 预留空间需要自己把握(预留空间多了,降低了内存回收频率,少了容易并发失败)
  • 标记清除算法的空间碎片

G1收集器(Garbage First收集器)

        Garbage First(简称G1)收集器是垃圾收集器技术发展历史上的里程碑式的成果,它开创了收集器面向局部收集的设计思路喝基于Region的内存布局形式。早在JDK7刚刚确立项目目标、Oracle公司制定的JDK7RoadMap里面,G1收集器就被视作JDK7中HotSpot虚拟机的一项重要进化特征。从JDK6 UPdate 14开始就有Early Access版本的G1收集器供开发人员实验和试用,但由此开始G1收集器的“实验状态”(Experimental)持续了数年时间,直至JDK7 Update4,Oracle才认为它达到足够成熟的商用程度,移除了"Experimental"的标识;到了JDK 8 Update40的时候,G1提供并发的类卸载的支持,补全了其计划功能的最后一块拼图。这个版本以后的G1收集器才被Oracle官方称为“全功能的垃圾收集器”(Fully-Featured Garbage Collector)。

特点

  • 是一款主要面向服务端应用的垃圾收集器
  • G1把内存划分为多个独立的区域Region
  • G1仍然保留分代思想,保留了新生代和老年代,但他们不再是物理隔离,而是一部分Region的集合
  • G1能够充分利用多CPU、多核环境硬件优势,尽量缩短STW
  • G1整体采用标记整理算法,局部是采用复制算法,不会产生内存碎片
  • G1的停顿可预测,能够明确指定在一个时间段内,消耗在垃圾收集上的时间不超过设置时间
  • G1跟踪各个Region里面垃圾的价值大小,会维护一个优先列表,每次根据允许的时间来回收价值最大的区域,从而保证在有限事件内高效的收集垃圾

Region中的特殊区域

        Region中的特殊区域Humongous(巨大的),专门用来存储大对象

        判定条件:G1认为只要大小超过了一个Region容量一半的对象即可判定为大对象。

        超过了整个Region容量的超级大对象,将会被存放在N个连续的Humongous Region之中,G1的大多数行为都把Humongous Region作为老年代的一部分进行看待。

Region区域

        G1不再坚持固定大小以及固定数量的分代区域划分,而是把连续的Java堆划分为多个独立区域(Region),每一个Region都可以根据需要,扮演新生代的Eden空间、Survivor空间或者老年代空间。将整个堆空间细分为若干个小的区域。

        JVM垃圾收集器与HotSpot的算法细节_第6张图片

  1.  使用G1收集器时,它将整个Java堆划分成约2048个大小相同的独立Region块,每个Region块大小根据堆空间的实际大小而定,为2的N次幂,即1MB,2MB,4MB,8MB,16MB,32MB.
  2. 虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region(不需要连续)的集合。通过Region的动态分配方式实现逻辑上的连续。
  3. G1垃圾收集器还增加了一种新的内存区域,叫做Humongous内存区域,图中H块。主要用于存储大对象,如果超过1.5个region,就放到H。一般被视为老年代。

G1 GC过程

G1提供了两种GC模式,Young GC和Mixed GC,两种均是完全Stop The World的。

  • Young GC:选定所有年轻代里的Region。通过控制年轻代的region个数,即年轻代内存大小,来控制young GC的时间开销。
  • Mixed GC:选定所有年轻代里的Region,外加根据globa concurrent marking统计得出收集收益高得若干老年代Region。在用户指定的开销目标范围内尽可能选择收益高的老年代Region。

在G1 GC垃圾回收的过程一个有四个阶段

  • 初始标记:和CMS一样只标记GC Roots直接关联的对象
  • 并发标记:进行GC Roots Traceing过程
  • 修正并发标记期间,因程序运行导致发生变化的那一部分对象
  • 筛选回收:根据时间来进行价值最大化收集

JVM垃圾收集器与HotSpot的算法细节_第7张图片

 G1 Young GC

Young GC执行前

堆分为大约2000个区域。最小大小为1Mb,最大大小为32Mb。蓝色区域保存老年代对象,绿色区域保存年轻对象。

                JVM垃圾收集器与HotSpot的算法细节_第8张图片

 执行Young GC

将存活的对象(即复制或移动)到一个或多个幸存者区域。如果满足老化阈值,则某些对象将被提升到老年代区域。

                JVM垃圾收集器与HotSpot的算法细节_第9张图片

 G1的年轻GC结束

                JVM垃圾收集器与HotSpot的算法细节_第10张图片

 最近升级的对象以深蓝色显示。幸存者区域为绿色。

总而言之,关于G1的年轻一代,以下几点:

  1. 堆是单个内存空间,分为多个Region区域。
  2. 年轻代内存由一组非连续区域组成。
  3. 年轻一代的垃圾收集器或年轻的GC出现STW。将停止所有应用程序线程以进行操作。
  4. 年轻的GC使用多个线程并行完成。
  5. 将活动对象复制到新的幸存者或老年代的地区。

G1 Mix GC

初始标记阶段(initial mark,STW)

存活的对象的初始标记背负在年轻的垃圾收集器上。在日志中,此标记为 GC pause(young)(inital-mark)

                JVM垃圾收集器与HotSpot的算法细节_第11张图片

 并发标记阶段(Concurrent Marking)

如果找到空白区域(如“X”所示),则在Remark阶段将其立即删除。另外,计算确定活跃度的信息。

                JVM垃圾收集器与HotSpot的算法细节_第12张图片

 最终标记阶段(Remark,STW)

空区域将被删除并回收。现在可以计算机所有区域的区域活跃度。

                JVM垃圾收集器与HotSpot的算法细节_第13张图片

 最终标记阶段(Remark,STW)

空区域将被删除并回收。现在可以计算所有区域的区域活跃度。

                JVM垃圾收集器与HotSpot的算法细节_第14张图片

 筛选回收阶段/复制清理阶段(Cleanup,STW)

G1选择“活跃度”最低的区域,这些区域可以被最快地收集。然后与年轻的GC同时收集这些区域。这在日志中表示为[GC pause (mixed)]。因此,年轻代和老年代都是同时收集的。

                JVM垃圾收集器与HotSpot的算法细节_第15张图片

 筛选回收阶段-(复制/清理)阶段之后

选定的区域已被收集并压缩为图中所示的深蓝色区域和深绿色区域。

        ​​​​​​​        JVM垃圾收集器与HotSpot的算法细节_第16张图片

 总结

  • 并发标记阶段
    • 活动信息是在应用程序运行时同时计算的。
    • 该活动信息标识在疏散暂停期间最合适回收的区域。
    • 像CMS中没有清扫阶段。
  • 最终标记阶段
    • 使用开始快照(SATB)算法,该算法比CMS使用的算法快得多。
    • 完全回收空区域。
  • 筛选回收阶段
    • 同时回收年轻一代和老一代。
    • 老年代地区是根据其活跃度来选择的。

G1常用参数

参数/默认值 含义
-XX:+UseG1GC 使用 G1 垃圾收集器
-XX:MaxGCPauseMillis=200 设置期望达到的最大GC停顿时间指标(JVM会尽力实现,但不保 证达到)
-XX:InitiatingHeapOccupancyPercent=45 mixed gc中也有一个阈值参数 ,当老年代大小占整个堆大小百分 比达到该阈值时,会触发一次mixed gc. 默认值为 45
-XX:NewRatio=n 新生代与老生代(new/old generation)的大小比例(Ratio). 默认值为 2.
-XX:SurvivorRatio=n eden/survivor 空间大小的比例(Ratio). 默认值为 8.
-XX:MaxTenuringThreshold=n 提升年老代的最大临界值(tenuring threshold). 默认值为 15.
-XX:ParallelGCThreads=n 设置垃圾收集器在并行阶段使用的线程数,默认值随JVM运行的平 台不同而不同
-XX:ConcGCThreads=n 并发垃圾收集器使用的线程数量. 默认值随JVM运行的平台不同而 不同.
-XX:G1ReservePercent=n 设置堆内存保留为假天花板的总量,以降低提升失败的可能性. 默认 值是 10.
-XX:G1HeapRegionSize=n 使用G1时Java堆会被分为大小统一的的区(region)。此参数可以指 定每个heap区的大小. 默认值将根据 heap size 算出最优解. 最小值 为 1Mb, 最大值为 32Mb.

作者:筱白爱学习!!

欢迎关注转发评论点赞沟通,您的支持是筱白的动力!​​​​​​​

你可能感兴趣的:(爱学习→Java,java,jvm,开发语言)