死磕 Netty 之内存篇:内存对齐类 SizeClasses

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 行,每列含义如下:

  1. index:由 0 开始的自增序列号,表示每个 size 类型的索引。

  2. log2Group:表示每个 Size 所在的组,以每 4 行为一组,总共 19 组,第 0 组比较特殊,值为 4,从第 1 组开始,其值为 6 ,后面每组自增 1;

  3. log2Delta:表示当前序号所对应的 size 和前一个序号所对应的 size 的差值的 log2 的值。

    1. 比如 index = 4,对应 size 为 80,index = 3 对应 64,所以 log2Delta(4) = log2(80 - 64) = 4
    2. 这里你仔细观察,你会发现从第 1 组开始,log2Delta 总是比 log2Group 小 1,而且在源码中 log2Delta 也不是通过复杂的计算方式得到的,而是通过很简单的递增方式。
  4. nDelta:表示组内增量的倍数,你可以简单地认为就是每个组内的序号

    1. 比如第 7 组 ,index 对应的( 8、 9、10、 11),对应 nDelta 为 (1 、2 、3、 4),第 0 组依然特殊,它从 0 开始。
  5. isMultiPageSize:表示当前 Size 是否为 PageSize(8KB = 8192) 的整数倍。

  6. isSubPage:表示当前 Size 是否为 SubPage 类型。

  7. 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)

对其进行推导:

从这个公式可以得出两个结论:

  1. 每一组的 log2Delta 是相同的, nDelta 的取值范围为 [1,4],所以 4 + nDelta的取值范围为 [5,8],也就是说同一组的 Size 是按照 {5,6,7,8} 倍来增长的,注意第 0 组是{4,5,6,7} 倍来增长。我们简单来验算下:

    1. 第 1 组{indx = 4 5 6 7},其 log2Delta = 4,导入公式 index4 > size = 2^4*5 = 80 、index4 > size = 2^4*6 = 96index4 > size = 2^4*7 = 112index4 > size = 2^4*8 = 128
  2. 由于每组都是以固定倍数递增,你会发现 nDelta 相同情况下,后一组是前一组的两倍:

    1. 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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值