image.png

ByteBuf分类:

1、根据内存创建方式(是否池化)可以分为 Pooled Unpooled
2、根据是否依赖 JDK 底层的 UnSafe 可以分为UnSafe非UnSafe方式。如果是 UnSafe,可以在JVM中通过JDK的UnSafe方法直接进行读写(例如PooledUnsafeHeapByteBuf),最终是通过内存地址+偏移量的方式获取数据;非UnSafe不会依赖到JDK底层的UnSafe对象(例如PooledHeapByteBuf),是通过数组+下标的方式获取数据
3、根据内存分配方式分为 Heap DirectHeap 直接在堆上进行分配,分配出的内存可以直接被GC进行管理(例如PooledUnsafeHeapByteBuf);Direct 直接调用 JDK API进行分配,分配完的内存不收JVM控制,不会参与到GC过程,需要手动使用 release 方法进行回收(例如PooledDirectByteBuf

ByteBufAllocator:

  1. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/21405095/1650512535554-89094efa-c241-47ba-9e43-e4af1942f7f1.png#clientId=u5e5c07d7-40be-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=241&id=uf7d738d1&margin=%5Bobject%20Object%5D&name=image.png&originHeight=415&originWidth=950&originalType=binary&ratio=1&rotation=0&showTitle=true&size=110851&status=done&style=none&taskId=u5e4ef680-e89c-4e47-9c2f-390ecd41189&title=ByteBufAllocator%E7%BB%A7%E6%89%BF%E5%85%B3%E7%B3%BB&width=552.3333740234375 "ByteBufAllocator继承关系")<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/21405095/1650522895830-820065e7-4a5d-4019-8a41-20e9524b921f.png#clientId=ue1a82615-3e17-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=935&id=u4a0c731e&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1402&originWidth=2560&originalType=binary&ratio=1&rotation=0&showTitle=true&size=492763&status=done&style=stroke&taskId=ua7aafc57-2926-497f-80b6-f84c429759e&title=PooledByteBufAllocator%20%E5%88%9B%E5%BB%BA%20ByteBuf%20%E8%BF%87%E7%A8%8B&width=1706.6666666666667 "PooledByteBufAllocator 创建 ByteBuf 过程")

内存规格划分:

{]0(2NQAB]8Q({61@XLEAP3.png

使用 16M 作为 huge 分界点的原因:内存分配是以 Chunk 为基础单位。例如要分配一个1M的内存,就先需要先申请一个16M的 Chunk,然后在16M的Chunk内取1M的内存,将该1M内存作为连续内存放到 ByteBuf 中
使用 8k 作为 normal 划分的原因:内部使用 Page 进行内存分配,一开始 Chunk 申请的16M内存过大,会将其根据 Page 的形式进行切分,16M可以切分为2048个Page,如果需要分配16K就会选取两个相邻的Page,一个Page大小为8K
tiny 和 small 使用 subPage 进行内存分配,tiny以 16B的倍数 作为规格进行切分,small 以 512B、1K、2K、4K 作为规格进行切分。 subPage Page 的基础上进行切分,例如需要分配1K内存,只需要定位一个Page然后切分为8份,从中拿出一份空闲内存片段进行分配;需要分配512B内存,只需要定位一个Page(8K)然后除以512B,从结果中拿出一个空闲内存片段进行分配即可

内存名称 内存大小范围
tiny 0 ~ 512B
small 512B ~ 8K
normal 8K ~ 16M
huge 大于16M

Chunk的数据结构:

通过对 PoolThreadCache 的分析,可以看到根据双向链表的方式进行连接
image.png
image.png
image.png

subpage数据结构:

说明 subpage 也是根据双向链表的方式进行连接
image.png

数据结构总结:

从一个线程的 PoolThreadCache 获取到对应的 Arena 并从中拿到 ChunkList,再从 ChunkList 中取一个Chunk 进行分配,在 Chunk 进行内存分配时会进行判断,当需要分配的内存大小超过一个 Page 时,会以 Page为单位进行分配,如果需要分配的内存小于一个 Page 时,会将当前 Page 切分成多个 SubPage 进行内存划分

内存分配:

tiny、small、normal 级别:

4.1.73.Final 版本,在创建 tiny、small normal 级别内存时,其分配逻辑已经整合到一起
通过对 PoolArena allocate 方法进行解析可以整理出 page 级别内存分配主要分为三步:

  1. 1. 尝试在现有 Chunk 上进行分配
  2. 2. 创建一个 Chunk 进行内存分配
  3. 3. 初始化 PooledByteBuf

image.png
image.png

tiny 级别:

  1. Netty 4.1.5X 版本, tiny 有单独的分配逻辑:
  2. 1. 定位一个 SubPage 对象
  3. 2. 初始化 SubPage
  4. 3. 初始化 PooledByteBuf

image.png

ByteBuf的释放:

使用 release 方法手动对 ByteBuf 进行释放,其最终释放逻辑实现为 PooledByteBuf deallocate()
image.png