内存模型
G1是基于Region的堆内存布局实现的,把连续的Java堆划分为多个大小相等的独立区域(Region),每一个Region都可以根据需要,扮演新生代的Eden空间,Survivor空间,或者老年代空间。收集器能够对扮演不同角色的Region采用不同的策略去处理,这样可以使不同状态的对象都能获取很好的收集效果
Region中存在特殊的Humongous区域,用来存储大对象。G1认为只要大小超过了Region容量一半的对象即可判定为大对象,G1的大多数行为都把Humongous Region作为老年代的一部分来看待。以下是G1收集器Region分区示意图
设置每个Region的大小:-XX:G1HeapRegionSize
取值范围:1MB~32MB,且应为2的N次幂
记忆集
记忆集是用来解决跨引用问题,避免全堆作为GC Root扫描。在G1中,每个Region都要维护自己的记忆集,这些记忆集会记录下别的Region指向自己的指针,并标记这些指针分别在哪些卡页的范围之内。G1的记忆集在存储结构的本质上是一种哈希表,Key是别的Region的起始地址,Value是一个集合,存储卡表的索引号。
原始快照算法
在并发标记阶段时,由于收集线程与用户线程是同时运行的,所以用户线程容易对收集线程产生影响,比如:用户线程改变对象引用关系时,必须保证其不能打破原本的对象图结构,否则会导致标记结果出现错误。垃圾收集对用户线程的影响还体现在回收过程中新创建对象的内存分配上,程序要继续运行就肯定会持续有新对象被创建。
G1收集器是通过原始快照(SATB)算法来实现的。G1为每一个Region设计了两个名为TAMS的指针,把Region中的一部分空间划分出来用于并发回收过程中的新对象分配,并发回收时,新分配的对象的地址都必须要在这两个指针位置以上。
回收过程
G1的回收是以Region为单位进行的,G1收集器运作过程大致可划分为以下四个步骤:
- 初始标记:仅仅只是标记一下GC Roots能直接关联到的对象,并且修改TAMS指针的值,让下一阶段用户并发运行时,能正确地在可用的Region中分配对象,这个阶段需要停顿线程,但耗时很短。
- 并发标记:从GC Root开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象,这个阶段耗时较长,但可与用于程序并发执行。
- 最终标记:对用户线程做另一个短暂的暂停,用于处理并发阶段结束后,仍遗留下来的最后少量的SATB记录
- 筛选回收:负责更新Region的统计数据,对各个Region的回收价值和成本进行排序,然后回收掉价值最高的Region。这里是先把需要回收的那一部分Region的存活对象复制到空的Region中,再清理掉整个旧的Region的全部空间。需要暂停用户线程,由多条收集器线程并行完成
以下是G1收集器运行示意图:
回收算法
整体上是标记+整理算法,两个区域之间是复制算法
Full GC
当内存回收的速度赶不上内存分配的速度时,G1收集器就会被迫冻结用户线程执行,导致Full GC,而产生长时间的“Stop The World”