Netty 为了更好地管理内存,它将内存进行规格化,在基于 jemalloc3 版本是将整个内存划分为 Tiny、Small、Normal 和 Huge 四类,而基于 jemalloc4 的 Netty 则将 Tiny 去掉了,保留了 Small、Normal、Huge。那 Netty 是怎么确认申请内存的大小是哪个规格呢?内部又是如何那规划这些内存规格呢?这就要依仗一个极其重要的类了 SizeClass
。为什么说是极其重要呢,看下图吧:
SizeClass
简介
SizeClass 是什么?它在 Netty 内存池中扮演什么角色?
它是 Netty的内存对齐类,为 Netty 内存池中的内存块提供大小对齐,索引计算等服务方法。
它内部维护着一张二维数组表,该数组存储着与内存规格相关的详细信息,我们先看看这张表长什么样(部分):
整理了一份面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题
需要全套面试笔记的【点击此处即可】即可免费获取
整个二维数组有 7 列,76 行,每列含义如下:
-
index
:由 0 开始的自增序列号,表示每个 size 类型的索引。 -
log2Group
:表示每个 Size 所在的组,以每 4 行为一组,总共 19 组,第 0 组比较特殊,值为 4,从第 1 组开始,其值为 6 ,后面每组自增 1; -
log2Delta
:表示当前序号所对应的 size 和前一个序号所对应的 size 的差值的 log2 的值。- 比如 index = 4,对应 size 为 80,index = 3 对应 64,所以
log2Delta(4) = log2(80 - 64) = 4
。 - 这里你仔细观察,你会发现从第 1 组开始,
log2Delta
总是比log2Group
小 1,而且在源码中log2Delta
也不是通过复杂的计算方式得到的,而是通过很简单的递增方式。
- 比如 index = 4,对应 size 为 80,index = 3 对应 64,所以
-
nDelta
:表示组内增量的倍数,你可以简单地认为就是每个组内的序号- 比如第 7 组 ,index 对应的( 8、 9、10、 11),对应 nDelta 为 (1 、2 、3、 4),第 0 组依然特殊,它从 0 开始。
-
isMultiPageSize
:表示当前 Size 是否为 PageSize(8KB = 8192) 的整数倍。 -
isSubPage
:表示当前 Size 是否为 SubPage 类型。 -
log2DeltaLookup
:当index <= 27
时,其值和 log2Delta 相等,当index > 27
,其值为 0。
除了维护这个总表(sizeClasses)外,SizeClass 还维护着其他三张表:
sizeIdx2sizeTab
:维护着 index 和 size的关系。pageIdx2sizeTab
:维护着 pageSize 倍数的内存规格表。size2idxTab
:维护着小于等于 4KB(4096) 规格的表。
在下面的源码解析中,大明哥将带你详细来看看这四张表的生成以及使用。
在 SizeClass 的总表 sizeClasses 的 Size 字段由 log2Group、nDelta、log2Delta 三个字段共同决定,其公式如下:
size = 1 << log2Group + nDelta * (1 << log2Delta)
对其进行推导:
从这个公式可以得出两个结论:
-
每一组的 log2Delta 是相同的, nDelta 的取值范围为
[1,4]
,所以4 + nDelta
的取值范围为 [5,8],也就是说同一组的 Size 是按照 {5,6,7,8} 倍来增长的,注意第 0 组是{4,5,6,7} 倍来增长。我们简单来验算下:- 第 1 组{indx = 4 5 6 7},其 log2Delta = 4,导入公式
index4 > size = 2^4*5 = 80
、index4 > size = 2^4*6 = 96
、index4 > size = 2^4*7 = 112
、index4 > size = 2^4*8 = 128
。
- 第 1 组{indx = 4 5 6 7},其 log2Delta = 4,导入公式
-
由于每组都是以固定倍数递增,你会发现 nDelta 相同情况下,后一组是前一组的两倍:
size_后 / size_前 = 2^log2Delta_后*(4 + nDelta) / 2^log2Delta_前*(4 + nDelta) = 2 ^(log2Delta_后 - log2Delta_前) = 2
具体详细的生成过程,下面源码部分讲述。
源码分析
成员变量
SizeClasses 成员变量我们主要理解一些常量就可以了:
java
复制代码
// 最小规格的移位 // SizeClass 的最小规格为 16B,所以最小规格偏移量为:log2(16) = 4 static final int LOG2_QUANTUM = 4; // 每组的移位 // 以每 4 行为一组,所以 log2(4) = 2 private static final int LOG2_SIZE_CLASS_GROUP = 2; // 定义最大详细划分查找表的规格限制为4096,移位为12 // 因为 size2idxTab 详细划分查找表中,最大限制size大小为4096,所以这里 log2(4096) = 12 private static final int LOG2_MAX_LOOKUP_SIZE = 12; /** * 总表 sizeClasses 中的 7 列 */ // index private static final int INDEX_IDX = 0; // log2Group private static final int LOG2GROUP_IDX = 1; // log2Delta private static final int LOG2DELTA_IDX = 2; // nDelta private static final int NDELTA_IDX = 3; // isMultiPageSize private static final int PAGESIZE_IDX = 4; // isSubPage private static final int SUBPAGE_IDX = 5; // log2DeltaLookup private static final int LOG2_DELTA_LOOKUP_IDX = 6; // 一页的大小 默认8KB protected final int pageSize; // 一页的移位 默认13 protected final int pageShifts; // 一chunk的大小 默认16mb protected final int chunkSize; // 总规格数量 默认 76 final int nSizes; // subPage的数量 默认39 int nSubpages; // 页倍数的规格数量 默认40 int nPSizes; // 记录最大Subpage的 index 默认是38 int smallMaxSizeIdx; // 4096 private int lookupMaxSize; // sizeClasses 总表 private final short[][] sizeClasses; // 维护着 pageSize 倍数的内存规格表 private final int[] pageIdx2sizeTab; // 维护着 index 和 size的关系 private final int[] sizeIdx2sizeTab; // 维护着小于等于 4KB(4096) 规格的表 private final int[] size2idxTab;
构造函数
ini
复制代码
protected SizeClasses(int pageSize, int pageShifts, int chunkSize, int directMemoryCacheAlignment){ // chunkSize = 4MB = 4194394 // group = log2(4194394) + 1 - 4 = 19 int group = log2(chunkSize) + 1 - LOG2_QUANTUM; // 初始化总表 // 19 << 2 = 84 // sizeClasses = short[76][7] short[][] sizeClasses = new short[group << LOG2_SIZE_CLASS_GROUP][7]; int normalMaxSize = -1; int nSizes = 0; int size = 0; int log2Group = LOG2_QUANTUM; // 4 int log2Delta = LOG2_QUANTUM; // 4 int ndeltaLimit = 1 << LOG2_SIZE_CLASS_GROUP; // 4 // 初始化第 0 组 for (int nDelta = 0; nDelta < ndeltaLimit; nDelta++, nSizes++) { short[] sizeClass = newSizeClass(nSizes, log2Group, log2Delta, nDelta, pageShifts); sizeClasses[nSizes] = sizeClass; size = sizeOf(sizeClass, directMemoryCacheAlignment); } // 第 1 组,log2Group 从 6 开始 log2Group += LOG2_SIZE_CLASS_GROUP; //从第 1 组开始,初始化整个 sizeClasses 表 // log2Group 和 log2Delta 并没有通过复杂的计算来得到,而是采用非常简单的递增方式来实现 // 从第 1 组开始,log2Group 从 6 开始,log2Delta 从 4 开始 for (; size < chunkSize; log2Group++, log2Delta++) { // nDelta <= ndeltaLimit --> 限定每组个数为 4 for (int nDelta = 1; nDelta <= ndeltaLimit && size < chunkSize; nDelta++, nSizes++) { short[] sizeClass = newSizeClass(nSizes, log2Group, log2Delta, nDelta, pageShifts); sizeClasses[nSizes] = sizeClas