在学习Netty内存池之前,我们先了解一下Netty的内存对齐类SizeClasses,它为Netty内存池中的内存块提供大小对齐,索引计算等服务方法。

源码分析基于Netty 4.1.52

Netty内存池中每个内存块size都符合如下计算公式

size = 1 << log2Group + nDelta * (1 << log2Delta)

log2Group:内存块分组

nDelta:增量乘数

log2Delta:增量大小的log2值

SizeClasses初始化后,将计算chunkSize(内存池每次向操作系统申请内存块大小)范围内每个size的值,保存到sizeClasses字段中。

sizeClasses是一个表格(二维数组),共有7列,含义如下

index:内存块size的索引

log2Group:内存块分组,用于计算对应的size

log2Delata:增量大小的log2值,用于计算对应的size

nDelta:增量乘数,用于计算对应的size

isMultipageSize:表示size是否为page的倍数

isSubPage:表示是否为一个subPage类型

log2DeltaLookup:如果size存在位图中的,记录其log2Delta,未使用

sizeClasses负责计算sizeClasses表格

  1. private int sizeClasses() {
  2. int normalMaxSize = -1;
  3. int index = 0;
  4. int size = 0;
  5. // #1
  6. int log2Group = LOG2_QUANTUM;
  7. int log2Delta = LOG2_QUANTUM;
  8. int ndeltaLimit = 1 << LOG2_SIZE_CLASS_GROUP;
  9. // #2
  10. int nDelta = 0;
  11. while (nDelta < ndeltaLimit) {
  12. size = sizeClass(index++, log2Group, log2Delta, nDelta++);
  13. }
  14. log2Group += LOG2_SIZE_CLASS_GROUP;
  15. // #3
  16. while (size < chunkSize) {
  17. nDelta = 1;
  18. while (nDelta <= ndeltaLimit && size < chunkSize) {
  19. size = sizeClass(index++, log2Group, log2Delta, nDelta++);
  20. normalMaxSize = size;
  21. }
  22. log2Group++;
  23. log2Delta++;
  24. }
  25. //chunkSize must be normalMaxSize
  26. assert chunkSize == normalMaxSize;
  27. //return number of size index
  28. return index;
  29. }

LOG2_QUANTUM=4

LOG2_SIZE_CLASS_GROUP=2

#1 log2Group,log2Delta都是从LOG2_QUANTUM开始

ndeltaLimit为2^LOG2_SIZE_CLASS_GROUP,即内存块size以4个为一组进行分组

#2 初始化第0组

nDelta从0开始

sizeClass方法计算sizeClasses每一行内容

注意:第0组后log2Group增加LOG2_SIZE_CLASS_GROUP,而log2Delta不变

#3 初始化后面的size

nDelta从1开始

每组log2Group+1,log2Delta+1

log2Group=log2Delta+LOG2_SIZE_CLASS_GROUP代入计算公式中,得到

size = 1 << (log2Delta+LOG2_SIZE_CLASS_GROUP) + nDelta * (1 << log2Delta)

size = (nDelta + 2 ^ LOG2_SIZE_CLASS_GROUP) * (1 << log2Delta)

可以看到,每个内存块size都是(1 << log2Delta)的倍数

从第二组开始,每组内这个倍数依次是5,6,7,8

每组内相邻行大小增量为(1 << log2Delta),相邻组之间(1 << log2Delta)翻倍。

Netty默认的配置一个page的大小是2^13,即为8KB,默认的一个chunk的大小为16777216,即16MB。sizeClasses表格内存如下:

Netty内存池中管理了大小不同的内存块,对于这些不同大小的内存块,Netty划分为不同的等级Small,Normal,Huge。Huge是大于chunkSize的内存块,不在表格中,这里也不讨论。

sizeClasses表格可以分为两部分

  1. isSubPage为1的size为Small内存块,其他为Normal内存块。

    分配Small内存块,需要找到对应的index

    通过size2SizeIdx方法计算index

    比如需要分配一个90字节的内存块,需要从sizeClasses表格找到第一个大于90的内存块size,即96,其index为5。

  2. Normal内存块必须是page的倍数。

    将isMultipageSize为1的行取出组成另一个表格

PoolChunk中分配Normal内存块需求查询对应的pageIdx。

比如要分配一个50000字节的内存块,需要从这个新表格找到第一个大于50000的内存块size,即57344,其pageIdx为6。

通过pages2pageIdxCompute方法计算pageIdx。

下面看一下具体的计算方法

  1. public int size2SizeIdx(int size) {
  2. if (size == 0) {
  3. return 0;
  4. }
  5. // #1
  6. if (size > chunkSize) {
  7. return nSizes;
  8. }
  9. // #2
  10. if (directMemoryCacheAlignment > 0) {
  11. size = alignSize(size);
  12. }
  13. // #3
  14. if (size <= lookupMaxSize) {
  15. //size-1 / MIN_TINY
  16. return size2idxTab[size - 1 >> LOG2_QUANTUM];
  17. }
  18. // #4
  19. int x = log2((size << 1) - 1);
  20. // #5
  21. int shift = x < LOG2_SIZE_CLASS_GROUP + LOG2_QUANTUM + 1
  22. ? 0 : x - (LOG2_SIZE_CLASS_GROUP + LOG2_QUANTUM);
  23. int group = shift << LOG2_SIZE_CLASS_GROUP;
  24. // #6
  25. int log2Delta = x < LOG2_SIZE_CLASS_GROUP + LOG2_QUANTUM + 1
  26. ? LOG2_QUANTUM : x - LOG2_SIZE_CLASS_GROUP - 1;
  27. // #7
  28. int deltaInverseMask = -1 << log2Delta;
  29. int mod = (size - 1 & deltaInverseMask) >> log2Delta &
  30. (1 << LOG2_SIZE_CLASS_GROUP) - 1;
  31. return group + mod;
  32. }

#1 大于chunkSize,就是返回nSizes代表申请的是Huge内存块。

#2 不使用sizeClasses表格,直接将申请内存大小转换为directMemoryCacheAlignment的倍数,directMemoryCacheAlignment默认为0。

#3 SizeClasses将一部分较小的size与对应index记录在size2idxTab作为位图,这里直接查询size2idxTab,避免重复计算

size2idxTab中保存了(size-1)/(2^LOG2_QUANTUM) --> idx的对应关系。

从sizeClasses方法可以看到,sizeClasses表格中每个size都是(2^LOG2_QUANTUM) 的倍数。

#4 对申请内存大小进行log2的向上取整,就是每组最后一个内存块size。-1是为了避免申请内存大小刚好等于2的指数次幂时被翻倍。

log2Group = log2Delta + LOG2_SIZE_CLASS_GROUPnDelta=2^LOG2_SIZE_CLASS_GROUP代入计算公式,可得

lastSize = 1 << (log2Group + 1)

x = log2Group + 1

#5 shift, 当前在第几组,从0开始(sizeClasses表格中0~3行为第0组,4~7行为第1组,以此类推,不是log2Group)

x < LOG2_SIZE_CLASS_GROUP + LOG2_QUANTUM + 1,即log2Group < LOG2_SIZE_CLASS_GROUP + LOG2_QUANTUM ,满足该条件的是第0组的size,这时shift固定是0。

从sizeClasses方法可以看到,除了第0组,都满足shift = log2Group - LOG2_QUANTUM - (LOG2_SIZE_CLASS_GROUP - 1)

shift << LOG2_SIZE_CLASS_GROUP就是该组第一个内存块size的索引

#6 计算log2Delta

第0组固定是LOG2_QUANTUM

除了第0组,将nDelta = 2^LOG2_SIZE_CLASS_GROUP代入计算公式

lastSize = ( 2^LOG2_SIZE_CLASS_GROUP + 2^LOG2_SIZE_CLASS_GROUP ) * (1 << log2Delta)

lastSize = (1 << log2Delta) << LOG2_SIZE_CLASS_GROUP << 1

#7 前面已经定位到第几组了,下面要找到申请内存大小应分配在该组第几位

这里要找到比申请内存大的最小size。

申请内存大小可以理解为上一个size加上一个不大于(1 << log2Delta)的值,即

(nDelta - 1 + 2^LOG2_SIZE_CLASS_GROUP) * (1 << log2Delta) + n, 备注:0 < n <= (1 << log2Delta)

注意,nDelta - 1就是mod

& deltaInverseMask,将申请内存大小最后log2Delta个bit位设置为0,可以理解为减去n

>> log2Delta,右移log2Delta个bit位,就是除以(1 << log2Delta),结果就是(nDelta - 1 + 2 ^ LOG2_SIZE_CLASS_GROUP)

& (1 << LOG2_SIZE_CLASS_GROUP) - 1, 取最后的LOG2_SIZE_CLASS_GROUP个bit位的值,结果就是mod

size - 1,是为了申请内存等于内存块size时避免分配到下一个内存块size中,即n == (1 << log2Delta)的场景。

疑问:既然右移log2Delta个bit位,那为什么前面要将log2Delta个bit位设置为0?

第0组由于log2Group等于log2Delta,代入计算公式如下

1 << log2Delta + (nDelta - 1) * (1 << log2Delta) + n, 备注:0 < n <= (1 << log2Delta)

nDelta * (1 << log2Delta) + n

所以第0组nDelta从0开始,mod = nDelta

pages2pageIdxCompute方法计算pageIdx逻辑与size2SizeIdx方法类似,只是将LOG2_QUANTUM变量换成了pageShifts,这里不再重复。

SizeClasses是给PoolArena(内存池),PoolChunk(内存块)提供服务的,建议大家结合后面分析PoolArena,PoolChunk的文章一起理解。

如果大家对SizeClasses具体算法不感兴趣,只有理解SizeClasses类中利用sizeClasses表格,为PoolArena,PoolChunk提供计算index,pageIdx索引的方法,也可以帮助大家理解后面解析PoolArena,PoolChunk的文章。

下面贴出sizeClasses完整表格(可复制到Excle,以|分列)

  1. | index | log2Group | log2Delta | nDelta | isMultiPageSize | isSubPage | log2DeltaLookup | size | usize |
  2. | 0 | 4 | 4 | 0 | 0 | 1 | 4 | 16 | |
  3. | 1 | 4 | 4 | 1 | 0 | 1 | 4 | 32 | |
  4. | 2 | 4 | 4 | 2 | 0 | 1 | 4 | 48 | |
  5. | 3 | 4 | 4 | 3 | 0 | 1 | 4 | 64 | |
  6. | 4 | 6 | 4 | 1 | 0 | 1 | 4 | 80 | |
  7. | 5 | 6 | 4 | 2 | 0 | 1 | 4 | 96 | |
  8. | 6 | 6 | 4 | 3 | 0 | 1 | 4 | 112 | |
  9. | 7 | 6 | 4 | 4 | 0 | 1 | 4 | 128 | |
  10. | 8 | 7 | 5 | 1 | 0 | 1 | 5 | 160 | |
  11. | 9 | 7 | 5 | 2 | 0 | 1 | 5 | 192 | |
  12. | 10 | 7 | 5 | 3 | 0 | 1 | 5 | 224 | |
  13. | 11 | 7 | 5 | 4 | 0 | 1 | 5 | 256 | |
  14. | 12 | 8 | 6 | 1 | 0 | 1 | 6 | 320 | |
  15. | 13 | 8 | 6 | 2 | 0 | 1 | 6 | 384 | |
  16. | 14 | 8 | 6 | 3 | 0 | 1 | 6 | 448 | |
  17. | 15 | 8 | 6 | 4 | 0 | 1 | 6 | 512 | |
  18. | 16 | 9 | 7 | 1 | 0 | 1 | 7 | 640 | |
  19. | 17 | 9 | 7 | 2 | 0 | 1 | 7 | 768 | |
  20. | 18 | 9 | 7 | 3 | 0 | 1 | 7 | 896 | |
  21. | 19 | 9 | 7 | 4 | 0 | 1 | 7 | 1024 | 1K |
  22. | 20 | 10 | 8 | 1 | 0 | 1 | 8 | 1280 | 1.25K |
  23. | 21 | 10 | 8 | 2 | 0 | 1 | 8 | 1536 | 1.5K |
  24. | 22 | 10 | 8 | 3 | 0 | 1 | 8 | 1792 | 1.75K |
  25. | 23 | 10 | 8 | 4 | 0 | 1 | 8 | 2048 | 2K |
  26. | 24 | 11 | 9 | 1 | 0 | 1 | 9 | 2560 | 2.5K |
  27. | 25 | 11 | 9 | 2 | 0 | 1 | 9 | 3072 | 3K |
  28. | 26 | 11 | 9 | 3 | 0 | 1 | 9 | 3584 | 3.5K |
  29. | 27 | 11 | 9 | 4 | 0 | 1 | 9 | 4096 | 4K |
  30. | 28 | 12 | 10 | 1 | 0 | 1 | 0 | 5120 | 5K |
  31. | 29 | 12 | 10 | 2 | 0 | 1 | 0 | 6144 | 6K |
  32. | 30 | 12 | 10 | 3 | 0 | 1 | 0 | 7168 | 7K |
  33. | 31 | 12 | 10 | 4 | 1 | 1 | 0 | 8192 | 8K |
  34. | 32 | 13 | 11 | 1 | 0 | 1 | 0 | 10240 | 10K |
  35. | 33 | 13 | 11 | 2 | 0 | 1 | 0 | 12288 | 12K |
  36. | 34 | 13 | 11 | 3 | 0 | 1 | 0 | 14336 | 14K |
  37. | 35 | 13 | 11 | 4 | 1 | 1 | 0 | 16384 | 16K |
  38. | 36 | 14 | 12 | 1 | 0 | 1 | 0 | 20480 | 20K |
  39. | 37 | 14 | 12 | 2 | 1 | 1 | 0 | 24576 | 24K |
  40. | 38 | 14 | 12 | 3 | 0 | 1 | 0 | 28672 | 28K |
  41. | 39 | 14 | 12 | 4 | 1 | 0 | 0 | 32768 | 32K |
  42. | 40 | 15 | 13 | 1 | 1 | 0 | 0 | 40960 | 40K |
  43. | 41 | 15 | 13 | 2 | 1 | 0 | 0 | 49152 | 48K |
  44. | 42 | 15 | 13 | 3 | 1 | 0 | 0 | 57344 | 56K |
  45. | 43 | 15 | 13 | 4 | 1 | 0 | 0 | 65536 | 64K |
  46. | 44 | 16 | 14 | 1 | 1 | 0 | 0 | 81920 | 80K |
  47. | 45 | 16 | 14 | 2 | 1 | 0 | 0 | 98304 | 96K |
  48. | 46 | 16 | 14 | 3 | 1 | 0 | 0 | 114688 | 112K |
  49. | 47 | 16 | 14 | 4 | 1 | 0 | 0 | 131072 | 128K |
  50. | 48 | 17 | 15 | 1 | 1 | 0 | 0 | 163840 | 160K |
  51. | 49 | 17 | 15 | 2 | 1 | 0 | 0 | 196608 | 192K |
  52. | 50 | 17 | 15 | 3 | 1 | 0 | 0 | 229376 | 224K |
  53. | 51 | 17 | 15 | 4 | 1 | 0 | 0 | 262144 | 256K |
  54. | 52 | 18 | 16 | 1 | 1 | 0 | 0 | 327680 | 320K |
  55. | 53 | 18 | 16 | 2 | 1 | 0 | 0 | 393216 | 384K |
  56. | 54 | 18 | 16 | 3 | 1 | 0 | 0 | 458752 | 448K |
  57. | 55 | 18 | 16 | 4 | 1 | 0 | 0 | 524288 | 512K |
  58. | 56 | 19 | 17 | 1 | 1 | 0 | 0 | 655360 | 640K |
  59. | 57 | 19 | 17 | 2 | 1 | 0 | 0 | 786432 | 768K |
  60. | 58 | 19 | 17 | 3 | 1 | 0 | 0 | 917504 | 896K |
  61. | 59 | 19 | 17 | 4 | 1 | 0 | 0 | 1048576 | 1M |
  62. | 60 | 20 | 18 | 1 | 1 | 0 | 0 | 1310720 | 1.25M |
  63. | 61 | 20 | 18 | 2 | 1 | 0 | 0 | 1572864 | 1.5M |
  64. | 62 | 20 | 18 | 3 | 1 | 0 | 0 | 1835008 | 1.75M |
  65. | 63 | 20 | 18 | 4 | 1 | 0 | 0 | 2097152 | 2M |
  66. | 64 | 21 | 19 | 1 | 1 | 0 | 0 | 2621440 | 2.5M |
  67. | 65 | 21 | 19 | 2 | 1 | 0 | 0 | 3145728 | 3M |
  68. | 66 | 21 | 19 | 3 | 1 | 0 | 0 | 3670016 | 3.5M |
  69. | 67 | 21 | 19 | 4 | 1 | 0 | 0 | 4194304 | 4M |
  70. | 68 | 22 | 20 | 1 | 1 | 0 | 0 | 5242880 | 5M |
  71. | 69 | 22 | 20 | 2 | 1 | 0 | 0 | 6291456 | 6M |
  72. | 70 | 22 | 20 | 3 | 1 | 0 | 0 | 7340032 | 7M |
  73. | 71 | 22 | 20 | 4 | 1 | 0 | 0 | 8388608 | 8M |
  74. | 72 | 23 | 21 | 1 | 1 | 0 | 0 | 10485760 | 10M |
  75. | 73 | 23 | 21 | 2 | 1 | 0 | 0 | 12582912 | 12M |
  76. | 74 | 23 | 21 | 3 | 1 | 0 | 0 | 14680064 | 14M |
  77. | 75 | 23 | 21 | 4 | 1 | 0 | 0 | 16777216 | 16M |

如果您觉得本文不错,欢迎关注我的微信公众号,系列文章持续更新中。您的关注是我坚持的动力!

Netty源码解析 -- 内存对齐类SizeClasses的更多相关文章

  1. Netty源码解析 -- 内存池与PoolArena

    我们知道,Netty使用直接内存实现Netty零拷贝以提升性能, 但直接内存的创建和释放可能需要涉及系统调用,是比较昂贵的操作,如果每个请求都创建和释放一个直接内存,那性能肯定是不能满足要求的. 这时 ...

  2. Netty源码解析 -- PoolChunk实现原理

    本文主要分享Netty中PoolChunk如何管理内存. 源码分析基于Netty 4.1.52 内存管理算法 首先说明PoolChunk内存组织方式. PoolChunk的内存大小默认是16M,Net ...

  3. Netty源码解析 -- PoolSubpage实现原理

    前面文章说了PoolChunk如何管理Normal内存块,本文分享PoolSubpage如何管理Small内存块. 源码分析基于Netty 4.1.52 内存管理算法 PoolSubpage负责管理S ...

  4. Netty源码解析 -- PoolChunk实现原理(jemalloc 3的算法)

    前面文章已经分享了Netty如何实现jemalloc 4算法管理内存. 本文主要分享Netty 4.1.52之前版本中,PoolChunk如何使用jemalloc 3算法管理内存. 感兴趣的同学可以对 ...

  5. Netty源码解析—客户端启动

    Netty源码解析-客户端启动 Bootstrap示例 public final class EchoClient { static final boolean SSL = System.getPro ...

  6. Netty 源码解析(三): Netty 的 Future 和 Promise

    今天是猿灯塔“365篇原创计划”第三篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源码解析(二): Netty 的 Channel 当前:Ne ...

  7. Netty 源码解析(九): connect 过程和 bind 过程分析

    原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 今天是猿灯塔“365篇原创计划”第九篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源 ...

  8. Netty 源码解析(八): 回到 Channel 的 register 操作

    原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 今天是猿灯塔“365篇原创计划”第八篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源 ...

  9. Netty 源码解析(六): Channel 的 register 操作

    原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 今天是猿灯塔“365篇原创计划”第六篇. 接下来的时间灯塔君持续更新Netty系列一共九篇   Netty 源码解析(一 ):开始 Netty ...

随机推荐

  1. 链接WPA2-企业WIFI时出现无法链接到该网络,可以链接个人WIFI时的问题和解决方案

    因在一个问题上掉两次坑所以还是决定记录下来,方便以后查阅. 第一次因为要部署.net 应用程序要求使用TLS1.2,所以修改了操作系统的默认启用的安全协议类型,导致好多应用程序出问题. 第二次因为vs ...

  2. django—ORM相关

    常用的QuerySet方法 1.all() 查询表中所有数据,返回一个QuerySet对象列表 2.filter() 查询满足条件的数据,返回一个QuerySet对象列表 3.get() 查询指定的数 ...

  3. C#番外篇-SpinWait

    SpinWait封装常见旋转逻辑.在单处理器计算机上,始终使用 "生成" 而不是 "繁忙等待",在装有超线程技术的 Intel 处理器的计算机上,这有助于防止硬 ...

  4. springboot集成spring security(一)

    一,添加pom依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId& ...

  5. MongoDB分片 --- MongoDB基础用法(六)

    分片 在Mongodb里面存在另一种集群,就是分片技术,可以满足MongoDB数据量大量增长的需求. 当MongoDB存储海量的数据时,一台机器可能不足以存储数据,也可能不足以提供可接受的读写吞吐量. ...

  6. 【转】Event Driven Programming

    FROM: http://lazyfoo.net/tutorials/SDL/03_event_driven_programming/index.php Event Driven Programmin ...

  7. Struts2 S2-059 (CVE-2019-0230 )复现 及流量分析、特征提取

    一.简介 2020年08月13日,Apache官方发布了Struts2远程代码执行漏洞的风险通告,该漏洞编号为CVE-2019-0230,漏洞等级:高危,漏洞评分:8.5 二.漏洞描述 Struts2 ...

  8. Python爬虫之多线程

    详情点我跳转 关注公众号"轻松学编程"了解更多. 多线程 在介绍Python中的线程之前,先明确一个问题,Python中的多线程是假的多线程! 为什么这么说,我们先明确一个概念,全 ...

  9. LWJGL3的内存管理,第三篇,剩下的两种策略

    LWJGL3的内存管理,第三篇,剩下的两种策略 上一篇讨论的基于 MemoryStack 类的栈上分配方式,是效率最高的,但是有些情况下无法使用.比如需要分配的内存较大,又或许生命周期较长.这时候就可 ...

  10. ETCD核心机制解析

    ETCD整体机制 etcd 是一个分布式的.可靠的 key-value 存储系统,它适用于存储分布式系统中的关键数据. etcd 集群中多个节点之间通过Raft算法完成分布式一致性协同,算法会选举出一 ...