垃圾回收(3)G1的结构和概念

G1介绍

G1(Garbage First)算法,通过参数-XX:+UseG1GC来启用,该算法在JDK 7u4版本被正式推出,G1可以通过参数-XX:MaxGCPauseMillis控制GC暂停时间。

Region

在G1算法中,采用了另外一种完全不同的方式组织堆内存,堆内存被划分为多个大小相等的内存块(Region),每个Region是逻辑连续的一段内存,结构如下:

每个Region被标记了E、S、O和H,说明每个Region在运行时都充当了一种角色,其中H是以往算法中没有的,它代表Humongous,这表示这些Region存储的是巨型对象(humongous object,H-obj),当新建对象大小超过Region大小一半时,直接在新的一个或多个连续Region中分配,并标记为H。

堆内存中一个Region的大小可以通过-XX:G1HeapRegionSize参数指定,大小区间只能是1M、2M、4M、8M、16M和32M,总之是2的幂次方,默认把堆内存按照2048份均分,也就是默认2048个Region。比如-Xmx16g -Xms16g,G1就会采用16G / 2048 = 8M 的Region。

GC模式

G1中提供了三种模式垃圾回收模式,Young gc、Mixed gc 和 Full gc,在不同的条件下被触发。

1、Young gc

发生在年轻代的GC算法,一般对象(除了巨型对象)都是在eden region中分配内存,当所有eden region被耗尽无法申请内存时,就会触发一次young gc,这种触发机制和之前的young gc差不多,执行完一次young gc,活跃对象会被拷贝到survivor region或者晋升到old region中。
年轻代,默认占用堆大小由以下参数控制:

2、Mixed gc

当越来越多的对象晋升到老年代old region时,为了避免堆内存被耗尽,虚拟机会触发一个混合的垃圾收集器,即mixed gc,该算法并不是一个old gc,除了回收整个young region,还会回收一部分的old region,这里需要注意:是一部分老年代,而不是全部老年代,可以选择哪些old region进行收集,一般是选择回收效益最高的,也就是垃圾最多的region,从而可以对垃圾回收的耗时时间进行控制。

mixed gc中也有一个阈值参数 -XX:InitiatingHeapOccupancyPercent,当老年代大小占整个堆大小百分比达到该阈值时,会触发一次mixed gc.

3、Full gc

如果对象内存分配速度过快,mixed gc来不及回收,导致老年代被填满,就会触发一次full gc,G1的full gc算法就是单线程执行的serial old gc,会导致异常长时间的暂停时间,需要进行不断的调优,尽可能的避免full gc.

G1中的数据结构

1、TLAB(线程本地分配缓冲区Thread Local Allocation Buffer)

由于堆内存是应用程序共享的,应用程序的多个线程在分配内存的时候需要加锁以进行同步。为了避免加锁,提高性能每一个应用程序的线程会被分配一个TLAB。TLAB中的内存来自于G1年轻代中的内存分段。当对象不是Humongous对象,TLAB也能装的下的时候,对象会被优先分配于创建此对象的线程的TLAB中。这样分配会很快,因为TLAB隶属于线程,所以不需要加锁。当TLAB的剩余空间不满足分配需求,则重新申请一块TLAB空间。

2、Card (卡表)

1、很小的内存区域,G1将Java堆划分为相等大小的一个个区域,这个小的区域大小是512 Byte,称为Card
2、Card Table维护着所有的Card。Card Table的结构是一个字节数组,Card Table用这个数组映射着每一个Card
3、Card中对象的引用发生改变时,Card在Card Table数组中对应的值被标记为dirty,就称这个Card被脏化了
4、分配对象会占用物理上连续若干个卡片

3、Rset(已记忆集合)

1、每个Region初始化时,会初始化一个remembered set
2、RSet里面记录了引用——就是其他Region中指向本Region中所有对象的所有引用,也就是谁引用了我的对象即RSet需要记录的东西应该是 xx Region的 xx Card。
3、RSet其实是一个Hash Table,Key是其他的Region的起始地址,Value是一个集合,里面的元素是Card Table 数组中的index,既Card对应的Index,映射到对象的Card地址。

Region1和Region3中有对象引用了Region2的对象,则在Region2的Rset中记录了这些引用。

Rset实现过程

为了维护这些RSet,如果每次给引用类型的字段赋值都要更新RSet,这带来的额外开销实在太大,G1中采用post-write barrier(写后栅栏)和concurrent refinement threads(并发后台线程)实现了RSet的更新。

//假设对象young和old分别在不同的Region中
Object young = new Object();
old.p = young;

Java层面给old对象的p字段赋值young对象的前后,JVM会插入一个pre-write barrier(写前栅栏)和post-write barrier(写后栅栏)。

1、写前栅栏 Pre-Write Barrrier:即将执行一段赋值语句时,等式左侧对象将修改引用到另一个对象,那么JVM就需要在赋值语句生效之前,记录丧失引用的对象。
2、写后栅栏 Post-Write Barrrier:当执行一段赋值语句后,等式右侧对象获取了左侧对象的引用,同样需要记录

其中post-write barrier的最终动作如下:
1、找到该字段所在的位置(Card),并设置为dirty_card
2、如果当前是应用线程,每个Java线程有一个dirty card queue,把该card插入队列
3、除了每个线程自带的dirty card queue,还有一个全局共享的queue

赋值动作到此结束,接下来的RSet更新操作交由多个ConcurrentG1RefineThread并发完成,每当全局队列集合超过一定阈值后,ConcurrentG1RefineThread会取出若干个队列,遍历每个队列中记录的card,并进行处理,逻辑如下:

1、根据card的地址,计算出card所在的Region
2、如果Region不存在,或者Region是Young区,或者该Region在回收集合(CSet)中,则不进行处理
3、处理该card中的对象,将应用关系写入Rset中。

RSet有什么好处?
进行垃圾回收时,如果Region1有根对象A引用了Region2的对象B,显然对象B是活的,如果没有Rset,就需要扫描整个Region1或者其它Region,才能确定对象B是活跃的,有了Rset可以避免对整个堆进行扫描。

4、CSet(回收集合Collection Set)

1、它记录了GC要收集的Regions集合
2、在任意一次收集暂停中,CSet所有分区都会被释放,内部存活的对象都会被转移到分配的空闲Region中。
3、CSet包括需要收集的Eden Regions、Survivor Regions,而且还包括部分(1/8)Old Regions(混合收集的时候,收集年轻代的时候,会收集一部分老年代的Region)。

5、SATB(snapshot-at-the-beginning)

为了解决在并发标记过程中,存活对象漏标的情况,GC HandBook把对象分成三种颜色:

1、黑色:自身以及可达对象都已经被标记
2、灰色:自身被标记,可达对象还未标记
3、白色:还未被标记

所以,漏标的情况只会发生在白色对象中,且满足以下任意一个条件:

1、并发标记时,应用线程给一个黑色对象的引用类型字段赋值了该白色对象
2、并发标记时,应用线程删除所有灰色对象到该白色对象的引用

对于第一种情况,利用post-write barrier,记录所有新增的引用关系,然后根据这些引用关系为根重新扫描一遍
对于第二种情况,利用pre-write barrier,将所有即将被删除的引用关系的旧引用记录下来,最后以这些旧引用为根重新扫描一遍

1、SATB的概念

1、SATB是增量式标记清除垃圾收集器设计的一个标记算法。
2、SATB的标记优化主要针对标记-清除垃圾收集器的并发标记阶段。
3、CMS的i设计使得它在remark阶段必须重新扫描所有线程栈和整个young gen作为root。G1的SATB设计在remark阶段则只需要扫描剩下的satb_mark_queue。

2、SATB的作用

1、SATB保证了在并发标记过程中新分配对象不会漏标。
2、SATB中的stab_mark_queue,在引用关系发生变化时,利用pre-write barrier,把原引用保存到stab_mark_queue中,每个应用线程都有一个stab_mark_queue。

总结

SATB的算法,每个region有5个指针,比较复杂,没有找到太好的文章,没有太理解,还有待进一步了解~~~

原文地址:https://blog.51cto.com/janephp/2428670

时间: 2024-08-30 18:13:49

垃圾回收(3)G1的结构和概念的相关文章

java GC垃圾回收机制G1、CMS

CMS(Concurrent Mark-Sweep)是以牺牲吞吐量为代价来获得最短回收停顿时间.对于要求服务器响应速度的应用上,这种垃圾回收器非常适合.在启动JVM参数加上-XX:+UseConcMarkSweepGC ,这个参数表示对于老年代的回收采用CMS.CMS采用的基础算法是:标记—清除. 使用场景: 1.应用程序对停顿比较敏感,并且在应用程序运行的时候可以提供更大的内存和更多的CPU 2.在JVM中,有相对较多存活时间较长的对象(老年代比较大)会更适合使用CMS. 为解决CMS算法产生

浅析JAVA的垃圾回收机制(GC)

1.什么是垃圾回收? 垃圾回收(Garbage Collection)是Java虚拟机(JVM)垃圾回收器提供的一种用于在空闲时间不定时回收无任何对象引用的对象占据的内存空间的一种机制. 注意:垃圾回收回收的是无任何引用的对象占据的内存空间而不是对象本身.换言之,垃圾回收只会负责释放那些对象占有的内存.对象是个抽象的词,包括引用和其占据的内存空间.当对象没有任何引用时其占据的内存空间随即被收回备用,此时对象也就被销毁.但不能说是回收对象,可以理解为一种文字游戏. 分析: 引用:如果Referen

JVM(四) 垃圾回收

1. 堆内存结构 Java堆从GC的角度可以细分为:新生代(Eden区.From Survivor区和To Survivor区)和老年代. 1.1 新生代 新生代是用来存放新生的对象.一般占据堆的1/3空间.由于频繁创建对象,所以新生代会频繁触发MinorGC进行垃圾回收.新生代又分为Eden区.ServivorFrom.ServivorTo三个区. 1.1.1.Eden区Java新对象的出生地(如果新创建的对象占用内存很大,则直接分配到老年代).当Eden区内存不够的时候就会触发MinorGC

jvm的stack和heap,JVM内存模型,垃圾回收策略,分代收集,增量收集(转)

深入Java虚拟机:JVM中的Stack和Heap(转自:http://www.cnblogs.com/laoyangHJ/archive/2011/08/17/gc-Stack.html) 在JVM中,内存分为两个部分,Stack(栈)和Heap(堆),这里,我们从JVM的内存管理原理的角度来认识Stack和Heap,并通过这些原理认清Java中静态方法和静态属性的问题. 一般,JVM的内存分为两部分:Stack和Heap. Stack(栈)是JVM的内存指令区.Stack管理很简单,push

JVM内存管理和JVM垃圾回收机制

JVM内存管理和JVM垃圾回收机制(1) 这里向大家描述一下JVM学习笔记之JVM内存管理和JVM垃圾回收的概念,JVM内存结构由堆.栈.本地方法栈.方法区等部分组成,另外JVM分别对新生代和旧生代采用不同的垃圾回收机制. AD: 你对JVM内存组成结构和JVM垃圾回收机制是否熟悉,这里和大家简单分享一下,希望对你的学习有所帮助,首先来看一下JVM内存结构,它是由堆.栈.本地方法栈.方法区等部分组成,结构图如下所示. JVM学习笔记 JVM内存管理和JVM垃圾回收 JVM内存组成结构 JVM内存

JVM垃圾回收(下)

接着上一篇,介绍完了 JVM 中识别需要回收的垃圾对象之后,这一篇我们来说说 JVM 是如何进行垃圾回收. 首先要在这里介绍一下80/20 法则: 约仅有20%的变因操纵着80%的局面.也就是说:所有变量中,最重要的仅有20%,虽然剩余的80%占了多数,控制的范围却远低于"关键的少数". Java 对象的生命周期也满足也这样的定律,即大部分的 Java 对象只存活一小段时间,而存活下来的小部分 Java 对象则会存活很长一段时间. 因此,这也就造就了 JVM 中分代回收的思想.简单来说

JVM学习系列(二) 垃圾回收

如何判断对象是否可回收 引用计数法 1.概念:给对象中添加一个引用计数器,每当有一个地方引用他时,计数器的值+1,当引用失效的时候,计数器-1,任何时刻计数器为0的对象就是不可以在被使用的对象. 2.缺点:无法解决对象循环引用的问题(如下图) 可达性分析法 1.概念:垃圾回收根节点(GCRoot)向下搜索,搜索所走过的路径称为引用链,当一个对象对GCRoot没有任何的引用链时,代表当前对象不可用. 2.GCRoot包含的对象: 虚拟机栈(帧栈中的本地变量表)中的引用的对象 方法区类静态属性 所引

JVM垃圾回收机制总结(4) :新一代的垃圾回收算法

全文转载: http://pengjiaheng.iteye.com/blog/548472 垃圾回收的瓶颈 传统分代垃圾回收方式,已经在一定程度上把垃圾回收给应用带来的负担降到了最小,把应用的吞吐量推到了一个极限.但是他无法解决的一个问题,就是Full GC所带来的应用暂停.在一些对实时性要求很高的应用场景下,GC暂停所带来的请求堆积和请求失败是无法接受的.这类应用可能要求请求的返回时间在几百甚 至几十毫秒以内,如果分代垃圾回收方式要达到这个指标,只能把最大堆的设置限制在一个相对较小范围内,但

JVM调优总结(6):新一代的垃圾回收算法

垃圾回收的瓶颈 传统分代垃圾回收方式,已经在一定程度上把垃圾回收给应用带来的负担降到了最小,把应用的吞吐量推到了一个极限.但是他无法解决的一个问题,就是Full GC所带来的应用暂停.在一些对实时性要求很高的应用场景下,GC暂停所带来的请求堆积和请求失败是无法接受的.这类应用可能要求请求的返回时间在几百甚至几十毫秒以内,如果分代垃圾回收方式要达到这个指标,只能把最大堆的设置限制在一个相对较小范围内,但是这样有限制了应用本身的处理能力,同样也是不可接收的. 分代垃圾回收方式确实也考虑了实时性要求而