JVM垃圾回收之G1收集器

从 我们知道CMS是一种以获取最短回收停顿时间为目标的收集器,非常适合堆内存大、CPU核数多的服务器端应用。但是CMS并不完美,它有如下缺点:CMS必须要在老年代堆内存用尽之前完成垃圾回收,否则CMS回收失败时,将触发担保机制,

Serial old老年代串行收集器将会以STW的方式进行一次GC,从而造成较大停顿时间;并且标记清理算法无法整理空间碎片,老年代空间会随着应用时长被逐步耗尽,最后将不得不通过担保机制对堆内存进行压缩。

G1(Garbage First)垃圾收集器是当今垃圾回收技术最前沿的成果之一。同CMS一样,G1也是关注最小时延的垃圾回收器,也同样适合大尺寸堆内存的垃圾收集。G1收集器是基于标记整理算法,它不会产生内存碎片,没必要在收集完后,进行一次独占式的碎片整理工作。G1最大的特点是引入分区的思路,弱化了分代的概念,合理利用垃圾收集各个周期的资源,解决了其他收集器甚至CMS的众多缺陷。

JVM垃圾回收之G1收集器

分区

G1采用了分区(Region)的思路,将整个堆空间分成若干个大小相等的内存区域,每次分配对象空间将逐段地使用内存。因此,在堆的使用上,G1并不要求对象的存储一定是物理上连续的,只要逻辑上连续即可;每个分区也不会确定地为某个代服务,可以按需在年轻代和老年代之间切换。启动时可以通过参数-XX:G1HeapRegionSize=n指定分区大小(1MB~32MB,且必须是2的幂),默认将整堆划分为2048个分区。

卡片

在每个分区内部又被分成了若干个大小为512 Byte卡片(Card),标识堆内存最小可用粒度所有分区的卡片将会记录在全局卡片表(Global Card Table)中,分配的对象会占用物理上连续的若干个卡片,当查找对分区内对象的引用时便可通过记录卡片来查找该引用对象(见RSet)。每次对内存的回收,都是对指定分区的卡片进行处理。

G1采用RSet(Remembered Set)

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

堆 Heap

G1同样可以通过-Xms/-Xmx来指定堆空间大小。当发生年轻代收集(Young GC)或混合收集(Mixed GC)时,通过计算GC与应用的耗费时间比,自动调整堆空间大小。如果GC频率太高,则通过增加堆尺寸,来减少GC频率,相应地GC占用的时间也随之降低;目标参数-XX:GCTimeRatio即为GC与应用的耗费时间比,G1默认为9,而CMS默认为99,因为CMS的设计原则是耗费在GC上的时间尽可能的少。另外,当空间不足,如对象空间分配或转移失败时,G1会首先尝试增加堆空间,如果扩容失败,则发起担保的Full GC。Full GC后,堆尺寸计算结果也会调整堆空间。

Young GC年轻代收集

在分配一般对象(非巨型对象)时,当所有Eden region使用达到最大阀值并且无法申请足够内存时,会触发一次Young GC。每次Young GC会回收所有Eden以及Survivor区,并且将存活对象复制到Old区以及另一部分的Survivor区。会复制到Old区的标准就是在PLAB中得到的计算结果。因为Young GC会进行根扫描,所以会STW。

MixedGC混合收集

MixedGC是G1 GC特有的,跟Full GC不同的是Mixed GC只回收部分老年代的Region。MixedGC一般会发生在一次YoungGC后面,为了提高效率,MixedGC会复用YoungGC的全局的根扫描结果,因为这个STW过程是必须的,整体上来说缩短了暂停时间。

MixedGC的回收过程可以理解为Young GC后附加的全局concurrent marking,全局的并发标记主要用来处理old区(包含H区)的存活对象标记,过程如下:

JVM垃圾回收之G1收集器

如果不计算维护 Remembered Set 的操作,G1收集器大致可分为如下步骤:

初始标记:仅标记GC Roots能直接到的对象,并且修改TAMS(Next Top at Mark Start)的值,让下一阶段用户程序并发运行时,能在正确可用的Region中创建新对象。(需要线程停顿,但耗时很短。)

并发标记:从GC Roots开始对堆中对象进行可达性分析,找出存活对象。(耗时较长,但可与用户程序并发执行)

最终标记:为了修正在并发标记期间因用户程序执行而导致标记产生变化的那一部分标记记录。且对象的变化记录在线程Remembered Set Logs里面,把Remembered Set Logs里面的数据合并到Remembered Set中。(需要线程停顿,但可并行执行。)

筛选回收:对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划。(可并发执行)

Full GC

G1在对象复制/转移失败或者没法分配足够内存(比如巨型对象没有足够的连续分区分配)时,会触发Full GC。Full GC使用的是STW的Serial Old模式,所以一旦触发Full GC则会STW应用线程,并且执行效率很慢。JDK 8版本的G1是不提供Full GC的处理的。对于G1 GC的优化,很大的目标就是没有Full GC。

参数配置:

  • -XX:+UseG1GC 使用 G1 垃圾收集器。
  • -XX:MaxGCPauseMillis=200 设置期望达到的最大GC停顿时间指标(JVM会尽力实现,但不保证达到)。
  • -XX:InitiatingHeapOccupancyPercent=n 启动并发GC周期时的堆内存占用百分比。 G1之类的垃圾收集器用它来触发并发GC周期,基于整个堆的使用率,而不只是某一代内存的使用比。值为 0 则表示”一直执行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。

总结

G1是一款非常优秀的垃圾收集器,不仅适合堆内存大的应用,同时也简化了调优的工作。通过主要的参数初始和最大堆空间、以及最大容忍的GC暂停目标,就能得到不错的性能;同时,我们也看到G1对内存空间的浪费较高,但通过先收集尽可能多的垃圾(G1)的设计原则,可以及时发现过期对象,从而让内存占用处于合理的水平。


分享到:


相關文章: