likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 31


Netty 为了更好地管理内存,它将内存进行规格化,在基于 jemalloc3 版本是将整个内存划分为 Tiny、Small、Normal 和 Huge 四类,而基于 jemalloc4 的 Netty 则将 Tiny 去掉了,保留了 Small、Normal、Huge。那 Netty 是怎么确认申请内存的大小是哪个规格呢?内部又是如何那规划这些内存规格呢?这就要依仗一个极其重要的类了 SizeClass。为什么说是极其重要呢,看下图吧:

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

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

SizeClass

简介

SizeClass 是什么?它在 Netty 内存池中扮演什么角色?

它是 Netty的内存对齐类,为 Netty 内存池中的内存块提供大小对齐,索引计算等服务方法。

它内部维护着一张二维数组表,该数组存储着与内存规格相关的详细信息,我们先看看这张表长什么样(部分):

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

整个二维数组有 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)

对其进行推导:

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

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

  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 = 80index4 > 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 成员变量我们主要理解一些常量就可以了:

    // 最小规格的移位
    // 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;

构造函数

    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] = sizeClass;
                size = normalMaxSize = sizeOf(sizeClass, directMemoryCacheAlignment);
            }
        }

        // 断言判断 最后生成的 size值是否符合 chunkSize
        assert chunkSize == normalMaxSize;

        int smallMaxSizeIdx = 0;
        int lookupMaxSize = 0;
        int nPSizes = 0;
        int nSubpages = 0;
        for (int idx = 0; idx < nSizes; idx++) {
            short[] sz = sizeClasses[idx];
            // 是否为 pageSize 的整数倍
            if (sz[PAGESIZE_IDX] == yes) {
                nPSizes++;
            }
            // 是否为 subPage
            if (sz[SUBPAGE_IDX] == yes) {
                nSubpages++;
                smallMaxSizeIdx = idx;
            }
            // lookupMaxSize 记录值 = 4096
            if (sz[LOG2_DELTA_LOOKUP_IDX] != no) {
                lookupMaxSize = sizeOf(sz, directMemoryCacheAlignment);
            }
        }
        this.smallMaxSizeIdx = smallMaxSizeIdx;
        this.lookupMaxSize = lookupMaxSize;
        this.nPSizes = nPSizes;
        this.nSubpages = nSubpages;
        this.nSizes = nSizes;

        this.pageSize = pageSize;
        this.pageShifts = pageShifts;
        this.chunkSize = chunkSize;
        this.directMemoryCacheAlignment = directMemoryCacheAlignment;

        // 生成 index 和 size的关系
        sizeIdx2sizeTab = newIdx2SizeTab(sizeClasses, nSizes, directMemoryCacheAlignment);
        // 生成 pageSize 倍数的内存规格表
        pageIdx2sizeTab = newPageIdx2sizeTab(sizeClasses, nSizes, nPSizes, directMemoryCacheAlignment);     // 生成小于等于 4KB(4096) 规格的表
        size2idxTab = newSize2idxTab(lookupMaxSize, sizeClasses);
    }

注意:由于大明哥使用的 Netty 版本为 4.1.77.Final,chunkSize 默认为 4MB,而不是大部分网上教程的 16MB。

这里有几个核心方法需要来详细阐述:

  • newSizeClass():生成总表 sizeClasses 的某行数据。
  • sizeOf():计算 size。
  • newIdx2SizeTab():生成 sizeIdx2sizeTab 表。
  • newPageIdx2sizeTab():生成 pageIdx2sizeTab 表。
  • newSize2idxTab():生成 size2idxTab 表。

下面就一个方法一个方法的看吧。

newSizeClass() - 生成总表 sizeClasses 的行

private static short[] newSizeClass(int index, int log2Group, int log2Delta, int nDelta, int pageShifts) {
        // 是否为 pageSize 的整数倍
        short isMultiPageSize;
        // 大于说明 size 计算的最小单位都是大于 pageSize
        if (log2Delta >= pageShifts) {
            isMultiPageSize = yes;
        } else {
            int pageSize = 1 << pageShifts;
            int size = calculateSize(log2Group, nDelta, log2Delta);
            
            isMultiPageSize = size == size / pageSize * pageSize? yes : no;
        }
        
        // 计算 log2Delta
        int log2Ndelta = nDelta == 0? 0 : log2(nDelta);

        byte remove = 1 << log2Ndelta < nDelta? yes : no;
        // log2(size)  --- ①
        int log2Size = log2Delta + log2Ndelta == log2Group? log2Group + 1 : log2Group;
        if (log2Size == log2Group) {
            remove = yes;
        }
        // pageShifts + LOG2_SIZE_CLASS_GROUP = 15
        // 若 size < 2^15 = 32K 即为 subpage
        short isSubpage = log2Size < pageShifts + LOG2_SIZE_CLASS_GROUP? yes : no;
        
        // 当 size <= 4096(4K) 即为 log2Delta,否则为 0
        int log2DeltaLookup = log2Size < LOG2_MAX_LOOKUP_SIZE ||
                              log2Size == LOG2_MAX_LOOKUP_SIZE && remove == no
                ? log2Delta : no;
        
        // 生成行
        return new short[] {
                (short) index, (short) log2Group, (short) log2Delta,
                (short) nDelta, isMultiPageSize, isSubpage, (short) log2DeltaLookup
        };
    }

这段代码还是比较简单的,这里对 log2Size = log2(size) 进行了一次取巧。

在构造函数中,经过两个 for 循环就可以得到一个完整的 sizeClasses,下面大明哥把这个二维数组详情列出来:

indexlog2Grouplog2DeltanDeltaisMultiPageSizeisSubPagelog2DeltaLookupsizeusize
044001416
144101432
244201448
344301464
464101480
564201496
6643014112
7644014128
8751015160
9752015192
10753015224
11754015256
12861016320
13862016384
14863016448
15864016512
16971017640
17972017768
18973017896
1997401710241K
20108101812801.25K
21108201815361.5K
22108301817921.75K
23108401820482K
24119101925602.5K
25119201930723K
26119301935843.5K
27119401940964K
281210101051205K
291210201061446K
301210301071687K
311210411081928K
32131110101024010K
33131120101228812K
34131130101433614K
35131141101638416K
36141210102048020K
37141221102457624K
38141230102867228K
39141241003276832K
40151311004096040K
41151321004915248K
42151331005734456K
43151341006553664K
44161411008192080K
45161421009830496K
4616143100114688112K
4716144100131072128K
4817151100163840160K
4917152100196608192K
5017153100229376224K
5117154100262144256K
5218161100327680320K
5318162100393216384K
5418163100458752448K
5518164100524288512K
5619171100655360640K
5719172100786432768K
5819173100917504896K
591917410010485761M
602018110013107201.25M
612018210015728641.5M
622018310018350081.75M
632018410020971522M
642119110026214402.5M
652119210031457283M
662119310036700163.5M
672119410041943044M
6800000052428805M
6900000062914566M
7000000073400327M
7100000083886088M
720000001048576010M
730000001258291212M
740000001468006414M
750000001677721616M

再次注意:因为大明哥的版本是 4.1.77.final ,chunkSize 默认为 4MB,所以只到 index = 67

sizeOf() - 计算 size

sizeOf() 就是根据公式 size = 1 << log2Group + nDelta * (1 << log2Delta) 来计算 size,如下:

    private static int sizeOf(short[] sizeClass, int directMemoryCacheAlignment) {
        int log2Group = sizeClass[LOG2GROUP_IDX];
        int log2Delta = sizeClass[LOG2DELTA_IDX];
        int nDelta = sizeClass[NDELTA_IDX];

        int size = calculateSize(log2Group, nDelta, log2Delta);

        return alignSizeIfNeeded(size, directMemoryCacheAlignment);
    }
    
    private static int calculateSize(int log2Group, int nDelta, int log2Delta) {
        return (1 << log2Group) + (nDelta << log2Delta);
    }
    
    // 内存对齐,这里一般都是直接返回 size
    private static int alignSizeIfNeeded(int size, int directMemoryCacheAlignment) {
        if (directMemoryCacheAlignment <= 0) {
            return size;
        }
        int delta = size & directMemoryCacheAlignment - 1;
        return delta == 0? size : size + directMemoryCacheAlignment - delta;
    }

newIdx2SizeTab() - 生成 sizeIdx2sizeTab 表

newIdx2SizeTab() 用于生成 Idx2SizeTab 表,该表用于维护 index 和 size之间的关系对应关系。

    private static int[] newIdx2SizeTab(short[][] sizeClasses, int nSizes, int directMemoryCacheAlignment) {
        int[] sizeIdx2sizeTab = new int[nSizes];
        
        for (int i = 0; i < nSizes; i++) {
            short[] sizeClass = sizeClasses[i];
            sizeIdx2sizeTab[i] = sizeOf(sizeClass, directMemoryCacheAlignment);
        }
        return sizeIdx2sizeTab;
    }

注意这里的 nSizes 为 68 ,不是 76。生成的 Idx2SizeTab 表格如下:

indexsize(虚)Unit
016
132
248
364
480
596
6112
7128
8160
9192
10224
11256
12320
13384
14448
15512
16640
17768
18896
191024
201280
211536
221792
232048
242560
253072
263584
2740964KB
285120
296144
307168
3181928KB
321024010KB
331228812KB
341433614KB
351638416KB
362048020KB
372457624KB
382867228KB
393276832KB
404096040KB
414915248KB
425734456KB
436553664KB
448192080KB
459830496KB
46114688112KB
runSize131072128KB
48163840160KB
49196608192KB
50229376224KB
51262144256KB
52327680320KB
53393216384KB
54458752448KB
55524288512KB
56655360640KB
57786432768KB
58917504896KB
5910485761.0MB
6013107201.25MB
6115728641.5MB
6218350081.75MB
6320971522MB
6426214402.5MB
6531457283MB
6636700163.5MB
6741943044MB

newPageIdx2sizeTab() - 生成 pageIdx2sizeTab 表

newPageIdx2sizeTab() 用于生成pageIdx2sizeTab 表,该表维护着 pageSize 倍数的内存规格表:

    private static int[] newPageIdx2sizeTab(short[][] sizeClasses, int nSizes, int nPSizes,
                                            int directMemoryCacheAlignment) {
        int[] pageIdx2sizeTab = new int[nPSizes];
        int pageIdx = 0;
        for (int i = 0; i < nSizes; i++) {
            short[] sizeClass = sizeClasses[i];
            // 只要 pageSize 整数倍表
            if (sizeClass[PAGESIZE_IDX] == yes) {
                pageIdx2sizeTab[pageIdx++] = sizeOf(sizeClass, directMemoryCacheAlignment);
            }
        }
        return pageIdx2sizeTab;
    }

其实就是去 sizeClasses 总表中 isMultiPageSize 为 1 的行。生成的表如下:

pageIndexsizeUnitsizeClasses 中 index
081928KB31
11638416KB35
22457624KB37
33276832KB39
44096040KB40
54915248KB41
65734456KB42
76553664KB43
88192080KB44
99830496KB45
10114688112KB46
11131072128KB47
12163840160KB48
13196608192KB49
14229376224KB50
15262144256KB51
16327680320KB52
17393216384KB53
18458752448KB54
19524288512KB55
20655360640KB56
21786432768KB57
22917504896KB58
2310485761.0MB59
2413107201.25MB60
2515728641.5MB61
2618350081.75MB62
2720971522MB63
2826214402.5MB64
2931457283MB65
3036700163.5MB66
3141943044MB67

newSize2idxTab() - 生成 size2idxTab 表

newSize2idxTab() 用于生成 size2idxTab 表,该表维护着小于等于 4KB(4096) 规格的表:

    private static int[] newSize2idxTab(int lookupMaxSize, short[][] sizeClasses) {
        int[] size2idxTab = new int[lookupMaxSize >> LOG2_QUANTUM];
        int idx = 0;
        int size = 0;
        
        // 遍历规格小于 4096 的
        for (int i = 0; size <= lookupMaxSize; i++) {
            int log2Delta = sizeClasses[i][LOG2DELTA_IDX];
            // 目的是 求出 该规格 可以按照16划分多少份
            int times = 1 << log2Delta - LOG2_QUANTUM;
            // 生成表
            while (size <= lookupMaxSize && times-- > 0) {
                size2idxTab[idx++] = i;
                size = idx + 1 << LOG2_QUANTUM;
            }
        }
        return size2idxTab;
    }

生成的表如下:

idxindexsize
0016B
1132B
2248B
3364B
4480B
5596B
66112B
77128B
88144B
99160B
109176B
..........
250274016B
251274032B
252274048B
253274064B
254274080B
255274096B

到这了 SizeClass 分析完了,这篇文章最核心的就在于那四张表的生成,生成过程其实不复杂,就要是要理解这个过程,还有几个查询方法大明哥没有分析,留给后面在讲内存分配的时候再讲会更加合适。

转载自:https://juejin.cn/post/7385430107291238411
评论
请登录