ByteBuf分类:
1、根据内存创建方式(是否池化)可以分为 Pooled 和 Unpooled
2、根据是否依赖 JDK 底层的 UnSafe 可以分为UnSafe和非UnSafe方式。如果是 UnSafe,可以在JVM中通过JDK的UnSafe方法直接进行读写(例如PooledUnsafeHeapByteBuf),最终是通过内存地址+偏移量的方式获取数据;非UnSafe不会依赖到JDK底层的UnSafe对象(例如PooledHeapByteBuf),是通过数组+下标的方式获取数据
3、根据内存分配方式分为 Heap 和 Direct。Heap 直接在堆上进行分配,分配出的内存可以直接被GC进行管理(例如PooledUnsafeHeapByteBuf);Direct 直接调用 JDK API进行分配,分配完的内存不收JVM控制,不会参与到GC过程,需要手动使用 release 方法进行回收(例如PooledDirectByteBuf)
ByteBufAllocator:
![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 过程")
内存规格划分:
使用 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 的分析,可以看到根据双向链表的方式进行连接
subpage数据结构:
说明 subpage 也是根据双向链表的方式进行连接
数据结构总结:
从一个线程的 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. 尝试在现有 Chunk 上进行分配
2. 创建一个 Chunk 进行内存分配
3. 初始化 PooledByteBuf
tiny 级别:
在 Netty 4.1.5X 版本, tiny 有单独的分配逻辑:
1. 定位一个 SubPage 对象
2. 初始化 SubPage
3. 初始化 PooledByteBuf
ByteBuf的释放:
使用 release 方法手动对 ByteBuf 进行释放,其最终释放逻辑实现为 PooledByteBuf 的 deallocate()