大纲
1.G1垃圾回收器的分区(Region大小+G1分区+Region过大过小和计算)
2.Region大小的计算原理(先转字节然后确定2的n次幂再通过1左移n位)
3.新生代分区及自动扩展(新生代动态扩展机制)
4.停顿预测模型(衰减算法)保证预期停顿时间
1.G1垃圾回收器的分区(Region大小+G1分区+Region过大过小和计算)
(1)G1垃圾回收器的简单介绍(垃圾优先回收器)
(2)传统的分代模型和G1的内存模型对比
(3)G1如何设置HeapRegion(HR)的大小
(4)HR的大小会对垃圾回收造成什么影响以及为什么要设置成2的N次幂
(5)Region的大小到底是如何计算的
(1)G1垃圾回收器的简单介绍(垃圾优先回收器)
一.垃圾回收优先
G1垃圾回收器:也可以叫垃圾回收优先回收器(Garbage-First,G1)。一句话概括就是:这种垃圾回收器会优先回收垃圾,不会等空间全部占满然后再进行回收。
二.停顿预测模型
预测一次回收可以回收的分区数量,以满足我们对停顿时间的要求。G1最大的特点:可以设置每次垃圾回收时的最大停顿时间,以及指定在一个长度为M毫秒的时间片段内,垃圾回收时间不超N毫秒。
三.化整为零的分区机制
ParNew + CMS这种回收器的分区是:新生代、老年代、S区。G1则是把一整块内存分成N个相同大小、灵活可变的分区(Region),这种灵活可变的Region机制,是G1能够控制停顿时间的核心设计。
(2)传统的分代模型和G1的内存模型对比
一.HeapRegion是G1内存管理的基本单位
G1有如下几种的分区类型:
新生代分区:Young Heap Region
自由分区:Free Heap Region
老年代分区:Old Heap Region
大对象分区:Humongous Heap Region
其中新生代分区又可分为Eden和Survivor,大对象分区又可分为大对象头分区和大对象连续分区。
二.ParNew +CMS与G1的内存模型对比
ParNew + CMS优势:(小内存停顿很小)
它不用做很多复杂的分区管理,而且小内存垃圾回收不会造成很大停顿。
ParNew + CMS劣势:(大内存停顿很大)
如果给JVM分配64G内存,那么Eden区可能就有20~30G,回收一次Eden区可能就要2~3s,这时请求都可能直接超时了。
传统的分代模型是按照块状来做内存分配的,这种分配方式会存在不足,比如在大内存机器中,会出现一次GC时间过长,导致STW时间较长。严重情况下,还会对用户体验造成比较大的影响。
所以针对大内存场景,诞生了G1这种垃圾回收器。G1的分代模型,通过化整为零,将一个大内存块分割成N个小内存块。然后根据需求,动态分配给新生代、老年代、大对象来使用。同时G1会根据垃圾回收情况动态改变新生代的大小(Region个数),当然也可能会因此动态改变老年代、大对象分区的大小。
比如需要分配对象时:可能会对新生代进行扩展新增几个Region,也可能会减少几个Region。
比如需要垃圾回收时:如果发现回收时间比较长,GC压力太大,这时就可考虑少给一些Region,从而保证回收和程序运行的一个平衡。
(3)G1如何设置HeapRegion(HR)的大小
一.手动式设置
通过设置参数G1HeapRegionSize来指定大小,默认为0。Region的大小范围是1~32M,同时需要满足2的N次幂。
二.启发式推断
G1可以通过算法,根据堆内存大小、分区个数,自动计算出Region大小。
注意:Region的大小只能在1-32M之间,不能小于1M也不能大于32M,且要满足2的N次方,即1、2、4、8、16、32这几个值。如果指定的值不是这几个值,G1会根据一定的算法规则自动调整。
(4)HR大小的影响以及为什么要设置成2的N次幂
如果HR过大,那么一个HR可以存放多个对象,分配效率高,但是回收时所花费的时间长。如果HR过小,则会导致分配效率低下。
一.Region过小可能会影响对象分配的性能
比如Region只有256K,而系统运行创建的对象是几十到几百K,那么JVM在为创建的对象分配空间时:
第一.找到可以使用的Region的难度增加了
导致分配一个对象时要查找Region的次数增多。
第二.跨分区存储的概率增加了
分配对象时,可能需要找多个Region分区。分区越小,就说明同样的Region,可以存储的对象越少。还可能会出现稍微大一点的对象就超过了一个Region的大小,那么就只能跨分区存储。如果一个对象要用多个Region存储,这时分配对象的开销还是比较大的。
二.Region过大可能会影响GC的性能
比如Region为64M,那么JVM在进行GC回收时:
第一.Region回收价值的判定很麻烦
对大Region进行回收性价比判断要比小Region难。
第二.回收的判定过程会更加复杂
GC Roots时需要追踪标记对象,然后标记垃圾对象。如果遇到跨代、跨区的对象,还要做一些额外的处理。判断这些对象是否需要回收的过程就会更加复杂,导致回收时间更长。
所以要平衡对象分配效率和垃圾回收效率,设置合理的Region大小来保证对象分配和垃圾回收性能。
三.为什么要设置成2的N次幂
如果不设置为2的N次幂,那么:
一.可能会造成内存碎片内存浪费的问题
一般内存的分配都是几个G,比如2048M、4096M或者8G、32G等。如果一个Region = 3M、15M、23M,那么就不能整除出有多少个分区。有可能分区数量不是整数,从而导致内存碎片,有一部分内存没利用上。此外如果需要扩展内存,也是按照2的倍数去进行扩展的。
二.无法利用2进制计算速度快的特性
计算机底层是二进制的,如果使用非2的N次幂的数字。那么在计算Region数量或自动扩展Region数量时,会无法利用2进制计算速度快的特性。因为位运算速度非常快的,计算2 * 2只需要进行1 << 2,所以设置一个比较合理的Region大小很重要。
(5)Region的大小到底是如何计算的
一.堆分区的个数默认是2048个
计算RegionSize时,会直接用默认的2048计算。
二.堆内存的大小默认最大是96M,最小为0M
设置G1的InitialHeapSize相当于设置Xms,设置G1的MaxHeapSize相当于设置Xmx。
三.RegionSize大小的计算公式
最小堆和最大堆的平均大小除以2048,然后计算出的RegionSize大小需要在1~32范围。
region_size = max((InitialHeapSize + MaxHeapSize) / 2 / 2048, 1M)
同时region_size在[1, 32]范围
四.举几个例子
第一种:只指定Region大小,假如设置Region大小为2M。则G1的总内存大小为:2048 * 2M = 4G,分区个数2048。
第二种:指定堆内存大小,且最大值等于最小值。假如堆内存最大值设置为32G,最小值也设置为32G,则RegionSize = max((32G + 32G) / 2 / 2048), 1M) = 16M。
第三种:指定堆内存大小,且最大值不等于最小值。假如堆内存最大最设置为Xms = 128G,最小值设置为Xmx = 128G,则RegionSize = max((32G + 128G) / 2 / 2048, 1M) = 32M,并且由于G1垃圾回收器会自动计算分区个数,所以分区个数的范围在32G / 32M = 1024 ~ 128G / 32M = 4096之间。
2.Region大小的计算原理(先转字节再确定2的n次幂再通过1左移n位)
(1)RegionSize如果不符合规则G1怎么处理
(2)对齐的规则是什么+超过大小限制会怎么做
(3)从源码中探索一下具体的RegionSize实现
(4)基于RegionSize的分区数量的变动过程
(1)RegionSize如果不符合规则G1怎么处理
如果将RegionSize设置成3M、1.5M、64M,和给定范围不同会怎么样?如果堆内存给3G,计算出的RegionSize是非2的N次幂,G1会怎么处理?如果计算出的RegionSize是一个非2的N次幂,那么G1会自动和2^N对齐。
那么对齐的规则到底是什么?像1.5、1.9这种数字,肯定要对应不同的值,是四舍五入还是怎么操作?如果超过了大小范围会怎么办?超过G1的RegionSize上下限会怎么样?
(2)对齐的规则是什么+超过大小限制会怎么做
关键词:向2的N次幂对齐。也就是说计算RegionSize得到的结果不是2^N的话,那么就会向2^N对齐。具体的对齐规则:从计算得到的数字中找到数字里包含的最大的2^N幂。
举个例子:
如果计算出1.5M,则RegionSize就是1M。
如果计算出3M,则RegionSize就是2M。
如果计算出9M,则RegionSize就是8M。
对于手动设置的RegionSize规则也一样。
(3)从源码中探索一下具体的RegionSize实现
#define TARGET_REGION_NUMBER = 2048
#define MIN_REGION_SIZE = 1024 * 1024
void HeapRegion::setup_heap_region_size(size_t initial_heap_size, size_t max_heap_size) {
uintx region_size = G1HeapRegionSize;
// region_size如果没有指定,那么就进入如下if判断里面根据内存大小去自动计算
if (FLAG_IS_DEFAULT(G1HeapRegionSize)) {
size_t average_heap_size = (initial_heap_size + max_heap_size) / 2;
// 根据公式计算出region_size
// region_size = max((InitialHeapSize + MaxHeapSize)/ 2 / 2048, 1M),同时在[1,32]范围
region_size = MAX2(average_heap_size / TARGET_REGION_NUMBER, (uintx) MIN_REGION_SIZE);
}
// 下面这一行代码就是获取这个region_size的2的指数;
// 如果region_size = 2MB = 2 * 1024 * 1024 B = 2^21 B,那么region_size_log就是21
// 如果region_size = 1.5MB = 1.5 * 1024 * 1024 B,由于2^20 < 1.5 * 2^20 < 2 ^ 21,所以region_size_log就是20
// 如果region_size = 3MB = 3 * 1024 * 1024 B,由于2^21 < 1.5 * 2 * 1024 * 1024 < 2^22,所以region_size_log就是21
// 由于region_size的限制范围是[1,32],所以如果region_size = 64MB = 64 * 1024 * 1024 = 2^26,那么region_size_log就是26
int region_size_log = log2_long((jlong) region_size);
// 为了确保region_size是最大的2的n次幂的值
// 会用上面计算出来的最大的指数region_size_log进行位运算来计算出最终的region_size:
// 也就是用1左移region_size_log位,即:1 << region_size_log,因为1左移n位和1*2^n次方相同
region_size = ((uintx)1 << region_size_log);
// 确保region_size不能高于最低限制也不能高于最高限制,必须在范围[1,32]
if (region_size < MIN_REGION_SIZE) {
region_size = MIN_REGION_SIZE;
} else if (region_size > MAX_REGION_SIZE) {
region_size = MAX_REGION_SIZE;
}
// 重新计算region_size的2的指数
region_size_log = log2_long((jlong) region_size);
// 设置一些全局变量
guarantee(LogOfHRGrainBytes == 0, "we should only set it once");
LogOfHRGrainBytes = region_size_log;
guarantee(LogOfHRGrainWords == 0, "we should only set it once");
LogOfHRGrainWords = LogOfHRGrainBytes - LogHeapWordSize;
guarantee(GrainBytes == 0, "we should only set it once");
GrainBytes = (size_t)region_size;
guarantee(GrainWords == 0, "we should only set it once");
GrainWords = GrainBytes >> LogHeapWordSize;
guarantee((size_t) 1 << LogOfHRGrainWords == GrainWords, "sanity");
// 卡表的一些设置
guarantee(CardsPerRegion == 0, "we should only set it once");
CardsPerRegion = GrainBytes >> CardTableModRefBS::card_shift;
}
计算时,第一步会按照上面的公式进行计算,然后对计算结果进行修正。
如果region_size = 1.5M:
因为2^20 < 1.5 * 1024 * 1024 < 2^21,所以region_size_log = 20。计算出最大指数region_size_log,就会通过位运算来计算出region_size。所以1左移region_size_log位,就是region_size = 2^20 = 1M。
如果region_size = 64M:
因为2^26<=64M,所以region_size_log=26,region_size=2^26=64M。又因为Region的大小必须要在1-32M之间,所以最终region_size = 32M。
问题:由于region_size是一个固定值,而且TARGET_REGION_NUMBER也是固定值2048,那么怎么理解分区数量会跟随内存动态扩展来变化?因为堆内存会有个初始大小和最大大小,而且堆内存初始大小默认是0。
(4)基于RegionSize的分区数量的变动过程
首先要明确一点,我们从源码中可以看出来:在计算RegionSize时,会使用一个参数的默认值2048来计算RegionSize,然后RegionSize会被动态调整成一个合理的值。
所以2048只是一个默认值,在使用2048这个值完成计算后:如果RegionSize没有调整,并且堆内存不会动态扩展时,堆分区的数量才是2048,否则分区的数量是会动态变化的。
总结:如果要计算分区大小RegionSize,肯定需要HeapSize。有了HeapSize,就能自动计算出来有多少个分区。因为堆内存很可能会出现变化,所以分区数量会随着堆内存变化而变化。
average_heap_size = (initial_heap_size + max_heap_size) / 2
这其实就是G1扩展内存的方式,扩展新的分区以达到扩展内存的效果。
注意:G1不能手动指定分区个数。按照默认值计算,G1可以管理的最大内存为2048 * 32M = 64G。假设设置xms = 32G,xmx = 128G,则每个Region分区的大小为32M,Region分区个数动态变化范围从1024到4096个。
如果Region越大,那么分配效率就越高,回收效率越低、回收时间越长。
如果Region越小,那么分配效率就越低,回收效率越高、回收时间越短。
3.新生代分区及自动扩展(新生代动态扩展机制)
(1)G1基于逻辑分代模型的设计
(2)新生代内存分配方式
(3)应该如何设置G1的新生代内存大小
(4)G1是怎么扩展新分区的 + 有什么规则限制
(5)G1新生代扩展流程(新生代分区扩展流程)
(1)G1基于逻辑分代模型的设计
G1也基于分代模型来实现,JVM在设计G1时使用了逻辑分区的概念。即一部分Region属于新生代、一部分Region属于老年代、一部分Region属于大对象,还有一部分Region属于自由分区。G1给对象分配内存时,也是先进入新生代的Eden区进行分配。
(2)新生代内存分配方式
Xmx是堆内存最大值,Xms是堆内存最小值。InitialHeapSize是堆内存的初始大小,等于Xms,默认是0。MaxHeapSize是堆内存的最大值,等于Xmx,默认是96M。
一.参数指定方式
第一种:指定堆内存新生代的大小
具体参数:MaxNewSize、NewSize。
需要注意:如果设置了Xmn,在G1里则认为是设置了MaxNewSize = NewSize = Xmn。所以G1中如果设置了Xmn,说明新生代内存的大小是固定的。新生代大小固定则意味着YGC时,很有可能停顿预测模型没有办法生效。
停顿预测模型 + 动态调整机制,是G1能够控制停顿时间的关键,所以一般不会设置G1的Xmn。
第二种:指定新生代的占比
具体参数:NewRatio。这个参数是用来设置老年代比新生代的比例的,例如-XX:NewRatio=4代表老年代 : 新生代 = 4 : 1。
需要注意:如果只设置了NewRatio,则对于新生代而言,MaxNewSize = NewSize,也就是新生代最大值最小值相等,即新生代 = HeapSize / (NewRatio + 1)。如果设置了MaxNewSize、NewSize以及NewRatio,则忽略NewRatio。
一般在G1里,也不推荐直接指定新生代大小,并且指定成一个固定值。
二.G1启发式推断
第三种:没有指定新生代最大值和最小值,或者只设置其中一个
G1会根据G1MaxNewSizePercent的值和G1NewSizePercent的值来计算新生代大小,G1MaxNewSizePercent的默认值是60%,G1NewSizePercent的默认值是5%。
如果没有设置新生代的大小或只设置MaxNewSize和NewSize其中一个,此时新生代初始化的大小就是5%的堆内存空间,然后最大就是60%。
如果只设置了NewRatio,其实也无法达到自动计算新生代空间的效果。一般都是设定G1堆内存的大小即可,然后新生代比例、新生代内存大小,让G1自动进行推断。除非系统运行了很长时间,发现了一个非常合理的新生代范围。此时就可考虑设置新生代内存,但也要让MaxNewSize和NewSize不相等。比如MaxNewSize = 100,NewSize = 10。
三.老年代内存是多少
老年代内存没有一个固定大小,也没有具体的参数来设置。除非设置了NewRatio这个参数,因为这个参数会间接设置老年代的大小。
-XX:InitiatingHeapOccupancyPercent=45:
该参数代表老年代的内存占用45%时会触发Mixed GC,也就是混合回收。此时老年代内存使用的比例,默认最高就是45%。
(3)应该如何设置G1的新生代内存大小
必须要满足动态扩展机制 + 停顿预测模型,才能满足设置的停顿时间。
一.如何满足G1新生代的动态扩展机制
不要指定新生代大小为固定值、不要直接指定Xmn,也不要直接只设置NewRatio、不要指定MaxNewSize = NewSize。
如果确实需要设置新生代的值,那么可设置成范围。比如MaxNewSize = 100及NewSize = 10,但这个范围如果设置得不很合理,还是很有可能会有性能问题。
二.为什么要满足G1新生代的动态扩展
为满足设定的停顿时间,就要进行垃圾回收时间和程序运行时间的平衡。控制回收时间在一个范围内,根据回收时间和内存大小来综合计算。然后动态调整内存分区的占比,来满足回收时间。
如果不做动态调整,那么GC时间过长,就没办法满足停顿时间。动态增加,动态减少,才能调整到一个合理的值。一旦超过了时间范围,就再调整一下。G1新生代的动态扩展,可以实现:动态调整YGC所需要的时间。
下面是一个具体的示例分析,例如:
新生代500个Region,期望停顿时间100ms,新生代填满后要进行GC。多次GC后,G1发现GC时间都不是很长(50ms),系统运行时间也特别短。也就是GC频率比较高,但GC耗时非常短,说明此时的GC还不是很合理。G1希望让程序运行时间长一些、GC不要那么频繁、同时满足停顿时间。这时候就可以扩展分区,在新生代增加一些Region。原来新生代有500个Region,扩展为1000个Region。
多次GC后,G1发现GC时间特别长(100ms),甚至有时都超过停顿时间。这时说明GC压力太大了(所以GC时间才特别长),需要减少一些Region。这时就可以移除新生代的一些Region,让对象填满新生代的速度变快,系统程序运行的时间可以短一点。原来新生代有500个Region,缩减为250个Regiion,从而让GC的时间满足小于期望停顿时间。
三.新生代的动态扩展使用分区列表实现
例如直接设置MaxNewSize = 100M,NewSize = 10M,此时新生代最小值和最大值都指定了。如果最小值是10个分区(Region)占10M,最大值100个分区占100M。当需要扩展分区时(此时是最小分区数量),就需要拿一些分区给新生代。
每一种类型的分区都会有对应的一个分区列表:新生代分区列表、老年代分区列表、大对象分区列表、自由分区列表。
如果新生代需要扩展:这时就会从自由分区拿一些Region出来,加入到新生代分区列表中。如果自由分区列表没有Region了,无法给新生代提供分区了。这时就要找JVM拓展新分区,然后加入新生代分区列表中,继续分配。
问题:堆内存什么时候扩展新的分区?
**扩展分区时机一:**分配对象时发现不够用,会尝试扩展分区,扩展分区后才继续分配对象。
**扩展分区时机二:**会有一个线程专门抽样处理预测新生代Region数量有多少,并动态调整。也就是根据对象创建的速率,去预测新生代Region数量应该给多少,然后动态调整新生代。
**扩展分区时机三:**在GC之后可能会直接动态调整。
(4)G1是怎么扩展新分区的 + 有什么规则限制
一.扩展新分区的规则是什么
根据-XX:GCTimeRatio这个参数去控制。这个参数表示GC时间与应用运行时间比值,G1中这个值默认是9。意思是如果GC时间占应用运行时间比例不超10%,就不需要动态扩展。如果GC时间占比超过了这个阈值,就需要做动态扩展(自适应扩展空间)。
二.扩展的内存大小分区数量有什么限制
G1ExpandByPercentOfAvailable参数的默认值是20,表示每次扩展时都从未使用的内存中申请20%的空间。而且最小不能小于1M,最大不能超过已经使用内存的一倍。
例如现在堆内存最大是64G,使用了32G,准备要做一次扩展。那么就要从未使用的64G - 32G = 32G里面申请20%的空间出来。如果计算发现20%乘以未使用的内存,小于1M,此时就给1MB。
每次扩展的内存大小是未使用内存的20%,而且还要满足:每次扩展的内存大小的下限是1M,上限是当前已经分配的内存的一倍。
(5)G1新生代扩展流程(新生代分区扩展流程)
时机一:
新生代分区列表不够 -> 需要新生代分区列表扩展 -> 找自由分区列表 -> 自由分区列表不够 -> 从堆内存中申请新的分区 -> 加入新生代分区列表中
时机二:
后台线程抽样 -> 程序运行时间 : GC时间 < 9 : 1 (即GC时间超过10%) -> 自动扩展新生代分区列表 -> 找自由分区列表 -> 自由分区列表不够了 -> 从堆内存中申请新分区 -> 加入到新生代分区列表中
4.停顿预测模型(衰减算法)保证预期停顿时间
(1)G1新生代内存总结
(2)如何满足用户设定的停顿时间
(3)如何设计一个合理的预测算法
(4)基于衰减算法模型的垃圾回收过程
(1)G1新生代内存总结
新生代内存分配的方式:
一.参数指定方式
二.G1启发式推断
我们应该怎么设置G1新生代内存的大小?
一.不能随便设置参数破坏新生代动态扩展机制
二.满足用户设定的停顿时间(期望停顿时间)
三.空闲列表+扩展新分区实现新生代动态扩展
四.扩展新分区规则(未使用的20%) + 一次扩展的大小上下限(1M和一倍)
五.自适应扩展空间的依据是-XX:GCTimeRatio,系统运行:GC时间=9:1
(2)如何满足用户设定的停顿时间
期望停顿时间只是期望值,G1会努力在这个目标停顿时间内完成GC。但G1不能保证,即也可能完不成,比如设置的期望停顿时间太小。
一.首先要预测
预测在停顿时间范围(200ms)内,G1能回收多少垃圾?比如G1预测能在200ms内回收2G的垃圾,那就选择2G内存对应的的Region来进行回收。
二.预测的依据是什么
预测的依据是GC相关的历史数据,所以要获取历次GC相关的运行数据。比如曾经发生的GC、每次GC多久、回收多少垃圾、总的GC时间是多少。
三.应该怎么预测 + 拿到历史数据该怎么用
基本逻辑是:如果目标停顿时间短、就少收点分区,目标停顿时间长、就多收点分区。也就是说,必须要知道,回收能力是多少。
此时历次回收相关的历史数据就派上用场了。可以根据这些历史数据进行计算,看看平均每秒能回收多少垃圾。比如发生3次GC,总共用了200ms回收2G垃圾,那么回收能力是10G/s。然后结合停顿时间,就能计算这次GC在期望停顿时间下能回收多少垃圾。所以需要一个历史数据的分析算法,来帮助G1分析回收能力。
四.一个简单的历史数据的分析算法模型
求过去10次GC造成多少停顿时间,最终计算出平均每秒能回收多少垃圾。例如过去10次一共收集了10G内存,一共花费了1s,那么200ms能够回收的垃圾就是2G。于是就可以根据这个计算值,选择一定数量的Region分区。
根据内存动态扩展机制,线性算法是否合理?
由于新生代内存可能会动态增加至最大值,新生代和老年代的Region数量也可能在变化。新生代越小回收时间肯定越快,越大需要回收的时间必然越久。而且系统在不断运行时,有时候是高峰期,有时候是低谷期。所以直接简单粗暴的求平均是不合适的。
不能仅使用历次回收的总大小除以总回收时间的平均值作为回收能力,仅仅使用平均值来作为停顿预测模型其实是不太合理的,因为:
第一.G1本身是一个不断扩展的模型
第二.同时系统也一直在不断地运行,有时是高峰期,有时是低谷期
(3)如何设计一个合理的预测算法
一.距离本次预测越近的GC其影响权重就越高
比如已经发生了3次GC,现在要预测第4次GC。那么第一次权重是0.2,第二次权重是0.3,第三次GC的权重可能就是0.5。
二.G1使用了衰减标准差算法来实现距离本次预测越近权重越高
衰减标准差算法有一个衰减因子叫α,α是一个小于1的固定值。简单理解就是:衰减因子越小,那么最新的数据对结果的影响就越大,G1的停顿预测模型就是以衰减标准差为理论基础来实现的。
三.具体计算模型
衰减平均计算公式:
davg(n) = Vn, n = 1
davg(n) = (1 - α) * Vn + α * davg(n - 1), n > 1
上述公式中的α为历史数据权值,1-α为最近一次数据权值。α越小,最新的数据对结果影响越大,最近一次的数据对结果影响最大。
例如α = 0.6,GC次数为3,三次分别为:
第一次回收2G,用时200ms。
第二次回收5G,用时300ms。
第三次回收3G,用时500ms。
那么计算结果就如下:
davg(1) = 2G / 200ms
davg(2) = (1 - 0.6) * 5G / 300ms + 0.6 * 2G / 200ms
davg(3) = (1 - 0.6) * 3G / 500ms + 0.6((1 - 0.6) * 5G / 300ms + 0.6 * 2G / 200ms)
从这个演变过程中也能看出:计算出来的平均值davg(3)中,权重最大的就是最后一次GC。这样就可以以最合理最精准的方式,预测出本次GC在目标停顿时间范围内能回收多少垃圾。
(4)基于衰减算法模型的垃圾回收过程
在两种不同的预测模型中:很显然,衰减预测模型更能反应出当前JVM的GC运行情况。因此衰减预测模型可以更好地帮助G1完成垃圾回收,并且能更好地满足目标停顿时间。