097_G1垃圾回收器的分区
G1我们要讲什么?
(1)G1的一些基本原理,比如,分区原理,对象分配原理,跨分区存储的原理。
(2)G1的一些设计思想,比如停顿预测模型,regionSize设计,对象快速分配设计等
(3)G1的GC过程及原理,包括ygc,mixedgc,fullgc的一些流程、细节、源码等。
本节内容:
1、G1垃圾回收器的简单介绍(垃圾优先回收器)
(1)垃圾回收优先
G1垃圾回收器,也可以叫垃圾回收优先回收器(Garbage-First,G1),一句话概括就是,这种垃圾回收器,会优先回收垃圾,不会等到空间全部占满, 然后进行回收。
(2)停顿预测模型
停顿预测模型,预测一次回收可以回收的分区数量,以满足我们对停顿时间的要求
(3)化整为零的分区机制
Par New + CMS 这种回收器的分区是,新生代,老年代,s区。
G1是,把一整个大块儿的内存,分成n个相同大小的分区(region)比较灵活,这种灵活可变的region机制,是G1能够做到控制停顿时间的核心设计。
2、传统的分代模型和G1的内存模型对比
(1)HeapRegion G1垃圾回收器内存管理的基本单位,也是最小单位。
分区类型:
新生代分区:Young Heap Region YHR
自由分区:Free Heap Region FHR
老年代分区:Old Heap Region OHR
大对象分区:Humongous Heap Region HHR
(2)ParNew +CMS 与G1的内存模型对比

优势:它不用做很多复杂分区管理的相关的东西。并且,小内存的时候,垃圾回收其实不会造成很大的停顿。
劣势:用ParNew +CMS ,给64GB的内存给JVM,eden区可能直接,20-30GB,回收一次eden区要2-3s,请求都可能直接超时了,是不是就比较恐怖?
内存分代模型中,传统的分代模型是按照块儿状来做内存分配,这种分配管理的方式,会有一些不足。比如在大内存机器的场景下,会出现一次GC时间过长,导致stop the world时间较长,严重的情况下,会对用户体验造成比较大的影响。
针对大内存场景,诞生了G1这种垃圾回收器,G1管理内存的时候,化整为零,直接将内存块儿,分割成了n个小内存块儿,根据需求,动态的分配给新生代,老年代,大对象来使用,同时G1 会根据垃圾回收的情况动态改变新生代(包括老年代、大对象分区)的大小(region个数)。
比如,有对象需要分配的时候,就有可能会对新生代进行扩展--动态变化的意思,扩展(新增)几个region,也有可能是,减少一些region。垃圾回收,回收时间比较长,说明GC压力太大,这个时候,是不是可以考虑,少给一些分区。
保证回收和程序运行的一个平衡。
所以,我们第一个比较关键的知识点,就是和region,也就是分区有关。所以,我们来看看region分区相关的内容。
3、G1如何设置HeapRegion(HR)的大小?
(1)手动式——通过参数设置 G1HeapRegionSize,默认为0
Region的大小限制,范围,1-32MB,同时需要满足 1 2 4 8 16 32
(2)启发式推断——G1本身通过算法自动计算 G1根据内存(堆内存大小、分区的个数)的情况自动计算出region大小
注意:region的大小,只能在1-32MB之间,不能小于1MB,也不能大于32MB,并且要满足是2的n次方。即,1,2,4,8,16,32这几个值。如果指定的值不是这个范围内的值,G1会根据一定的算法规则自动调整。
4、思考:HR的大小会对垃圾回收造成什么影响?为什么要设置成1 2 4 8 16 32?
(1)如果Region过小,比如说,256KB? 造成对象分配的性能问题
系统运行,创建的对象,一般也有可能是几十到几百KB。
第一,找到一个可以使用的region难度增加
分配一个对象需要找region的次数就会越多。简单来讲,就是,大海捞针,创建对象洒洒水,找个region跑断腿……
第二,跨分区存储的概率增加
分配对象的时候,可能需要找多次region分区,分区越小,就说明,同样的内存大小,可以存储的对象太少,还可能会出现,大量的稍微大一点的对象,就超过了一个region的大小,那么就只能跨分区存储,一个对象,要用多个region去存储,这个时候,分配一个对象的开销还是会比较大。
(2)如果Region过大,比如说,64MB,128MB,256MB?造成GC性能问题。
第一:region回收价值的判定很麻烦。
Region一大块儿内存,做回收性价比判断?肯定比小region要难度大
第二:回收的判定过程会更加复杂。
GC ROOTs,追踪标记对象,然后标记垃圾对象,遇到跨代,跨区的存储,还要做一些额外的其他处理,导致回收需要的时间更长,在做垃圾回收的时候,是不是需要判断那些对象,需要回收。这个过程就会更加复杂。

为了平衡分配效率和回收的效率。在保证两者的性能的同时,设置一个合理的region分区大小。
(3)为什么要设置成 1 2 4 8 16 32这几个值的范围?2的n次方
第一,造成内存碎片,内存浪费的问题
一般内存的分配都是,几个G,比如2048 MB,4096MB,或者8G 32G这种的,如果搞一个region = 3MB、15MB、23MB,有多少个分区,不能整除?是不是有可能分区数量不是整数?这是不是有可能导致内存碎片,有一部分没有利用上?扩展内存,也是按照2的倍数去扩展的。底层内存是不是按照字节为单位,位这个东西。
第二,计算机底层无法利用2进制计算速度快的特性
并且,计算机底层我们都知道是二进制的,如果使用非2的n次幂的数,在region数量计算,自动扩展region数量的时候,除了内存碎片,有可能也无法利用计算机底层2进制计算的速度快的特性。(位运算速度非常快。)
2 * 2 ,1 << 2来做计算。
设置一个比较合理的region大小很重要。如何设置呢,让他能够计算出一个比较合理的值?
5、region的大小到底是如何计算的?
(1)堆分区的个数 默认2048个,这个数字会根据具体的内存大小自动计算。
计算regionsize的时候,会直接用这个默认的2048这个值。
(2)堆内存的大小 默认最大96MB,最小为0MB
设置InitialHeapSize相当于设置Xms,设置MaxHeapSize相当于设置Xmx
(3)计算公式:region_size = max((InitialHeapSize+MaxHeapSize)/ 2 / 2048,1MB)[1,32]最小值
(4)举几个例子:
第一种,只指定region大小:
假如region大小设置为2MB,则G1的总内存大小为,2048 * 2MB = 4GB 分区个数2048
第二种,指定堆内存大小,且最大值等于最小值:
假如设置堆内存大小: Xms,Xmx,并且Xms = Xmx=32GB,则regionSize = max((32GB+32GB)/2 / 2048),1MB) = 16MB
第三种,指定堆内存大小,且最大值不等于最小值:
假如设置堆内存大小Xms,Xmx,并且Xms = 32GB,Xmx=128GB,则RegionSize = max((32GB+128GB)/2/2048,1MB) = 32MB。并且由于G1垃圾回收器会自动计算分区个数,在这个例子中,分区的个数范围在32GB/32MB=1024 ~ 128GB/32MB=4096之间。
假如设置堆内存大小Xms,Xmx,并且Xms = 64GB,Xmx=256GB,大家自己计算一下。
6、regionSize如果不符合规则,G1是怎么处理的?
最后留一个问题,regionSize如果我设置成3MB,或者1.5MB,64MB,不符合G1的限制条件,会怎么样?或者说,如果我的堆内存给的是3G,然后计算出来的regionSize是一个非2的n次幂,G1会做什么处理?
会做动态调整regionsize。