从零开始实现简易版Netty(七) MyNetty 实现Normal规格的池化内存分配 从零开始实现简易版Netty(七) MyNetty 实现Normal规格的池化内存分配在Netty的高性能网络框架中内存管理是核心优化点之一。Netty通过PooledByteBufAllocator实现了内存池化减少了频繁的内存分配与回收开销。本文将从零开始实现一个简易版的池化内存分配器——MyNetty专注于Normal规格即非Tiny或Small的内存分配。我们将深入剖析其原理并配合可运行的代码示例帮助理解池化内存分配的设计思想。### 池化内存分配的基本原理池化内存分配的核心思想是预分配一大块连续内存称为Chunk并将其划分为多个固定大小的块称为Page或Subpage。当应用请求内存时分配器从已分配的内存池中查找合适的块返回避免频繁调用系统malloc和free。Netty将内存规格分为Tiny512字节、Small512字节8KB、Normal8KB16MB和Huge16MB。Normal规格的内存通常以8KB的Page为单位进行分配。MyNetty的实现将聚焦于Normal规格使用一个简单的内存池结构一个Chunk包含多个Page每个Page大小为8KB。分配时根据请求大小向上对齐到Page大小查找空闲Page。我们采用二叉伙伴分配算法Binary Buddy Allocator来管理Page的分配与合并以减少外部碎片。### 核心数据结构设计为了实现Normal规格的池化内存分配我们需要以下核心组件-PoolChunk管理一大块连续内存例如64KB包含多个Page。它维护一个二叉树数组来表示每个节点Page或Page组合的空闲状态。-PoolSubpage虽然Normal规格通常不涉及Subpage但为了统一我们简化设计让Normal分配直接返回Page。-PoolArena管理多个Chunk的内存分配请求类似Netty的PoolArena。二叉树伙伴算法的关键每个节点代表一个内存块父节点表示两个子节点合并后的块。节点状态通过一个字节数组memoryMap表示值越小表示块越大空闲值越大表示块越小已分配或部分分配。例如深度为0的根节点代表整个Chunk深度为d的节点代表大小为chunkSize / 2^d的块。### 代码实现PoolChunk与分配逻辑下面我们实现PoolChunk类它包含二叉树的构建、内存分配和释放功能。为了简化我们使用字节数组模拟内存但实际中应使用DirectByteBuffer或byte[]。javaimport java.util.concurrent.atomic.AtomicInteger;/** * 简易版PoolChunk使用二叉伙伴算法管理Normal规格内存。 * chunkSize必须为2的幂例如64KB65536字节。 */public class PoolChunk { private final byte[] memory; // 模拟内存块 private final int chunkSize; private final int maxOrder; // 二叉树深度例如chunkSize65536, pageSize8192时maxOrder3 (2^38 pages) private final byte[] memoryMap; // 空闲状态0表示完全空闲maxOrder表示已分配 private final AtomicInteger freeBytes; // 剩余字节数 public PoolChunk(int chunkSize, int pageSize) { this.chunkSize chunkSize; this.maxOrder log2(chunkSize / pageSize); this.memory new byte[chunkSize]; this.memoryMap new byte[1 (maxOrder 1)]; // 二叉树节点数 this.freeBytes new AtomicInteger(chunkSize); // 初始化memoryMap根节点为0完全空闲子节点递归初始 initMemoryMap(0, 0, maxOrder); } // 递归初始化memoryMap节点id从0开始depth为当前深度 private void initMemoryMap(int id, int depth, int maxDepth) { if (depth maxDepth) { memoryMap[id] (byte) maxDepth; // 叶子节点标记为maxDepth return; } memoryMap[id] (byte) depth; // 空闲状态为当前深度 initMemoryMap(id * 2 1, depth 1, maxDepth); initMemoryMap(id * 2 2, depth 1, maxDepth); } // 分配指定大小的内存返回偏移量模拟内存地址 public int allocate(int reqSize) { int pageCount (reqSize 8191) / 8192; // 向上对齐到Page int order log2(pageCount); // 需要的order例如1个page对应order0 if (order maxOrder) { throw new IllegalArgumentException(Request size too large); } // 查找空闲节点 int id allocateNode(order); if (id 0) { return -1; // 分配失败 } // 计算偏移量根据节点id计算在chunk中的位置 int offset calculateOffset(id, maxOrder); freeBytes.addAndGet(-(pageSizeFromOrder(order))); return offset; } // 二叉伙伴算法查找空闲节点 private int allocateNode(int order) { int id 0; // 从根开始 // 如果根节点无法满足需求返回-1 if (memoryMap[0] order) { return -1; } // 向下查找 while (memoryMap[id] order) { // 先尝试左子节点 int leftId id * 2 1; if (leftId memoryMap.length memoryMap[leftId] order) { id leftId; } else { id id * 2 2; } } // 标记节点为已分配更新memoryMap setValue(id, (byte) maxOrder); // 更新父节点状态 updateParents(id); return id; } // 更新父节点状态若两个子节点都已分配父节点标记为maxOrder否则取子节点最小值 private void updateParents(int id) { while (id 0) { int parentId (id - 1) / 2; int leftId parentId * 2 1; int rightId parentId * 2 2; byte leftVal memoryMap[leftId]; byte rightVal memoryMap[rightId]; byte minVal (byte) Math.min(leftVal, rightVal); if (memoryMap[parentId] ! minVal) { memoryMap[parentId] minVal; } id parentId; } } // 释放内存offset为之前分配的偏移量 public void free(int offset, int reqSize) { int pageCount (reqSize 8191) / 8192; int order log2(pageCount); // 根据偏移量计算节点id int id calculateId(offset, order, maxOrder); // 标记为空闲 setValue(id, (byte) order); updateParents(id); freeBytes.addAndGet(pageSizeFromOrder(order)); } // 辅助方法设置memoryMap值 private void setValue(int id, byte val) { if (id memoryMap.length) { memoryMap[id] val; } } // 计算节点对应的内存偏移量 private int calculateOffset(int id, int maxOrder) { int offset 0; int depth 0; while (id 0) { int parent (id - 1) / 2; if (id parent * 2 2) { // 右子节点 offset chunkSize / (1 (depth 1)); } id parent; depth; } return offset; } // 根据偏移量和order计算节点id简化实现 private int calculateId(int offset, int order, int maxOrder) { // 实际需根据二叉树路径计算这里简化假定offset对应第几个page块 int pageIndex offset / 8192; int totalPages chunkSize / 8192; // 计算叶子节点id叶子节点从索引totalPages-1开始 int leafStart (1 maxOrder) - 1; // 叶子节点起始id return leafStart pageIndex; } private int log2(int value) { return 31 - Integer.numberOfLeadingZeros(value); } private int pageSizeFromOrder(int order) { return (chunkSize (maxOrder - order)); } public int freeBytes() { return freeBytes.get(); }}### 代码示例使用PoolChunk进行分配与释放下面是一个测试类展示如何使用PoolChunk进行Normal规格的内存分配和释放。javapublic class MyNettyPoolExample { public static void main(String[] args) { // 创建一个Chunk大小64KBPage大小8KBmaxOrder3共8个Page int chunkSize 64 * 1024; // 64KB int pageSize 8 * 1024; // 8KB PoolChunk chunk new PoolChunk(chunkSize, pageSize); System.out.println(初始空闲字节: chunk.freeBytes() bytes); // 分配一个Normal请求16KB需要2个Page int offset1 chunk.allocate(16 * 1024); System.out.println(分配16KB偏移量: offset1); System.out.println(剩余空闲: chunk.freeBytes() bytes); // 分配另一个8KB请求 int offset2 chunk.allocate(8 * 1024); System.out.println(分配8KB偏移量: offset2); System.out.println(剩余空闲: chunk.freeBytes() bytes); // 释放第一个16KB chunk.free(offset1, 16 * 1024); System.out.println(释放16KB后空闲: chunk.freeBytes() bytes); // 分配32KB需要4个Page int offset3 chunk.allocate(32 * 1024); System.out.println(分配32KB偏移量: offset3); System.out.println(剩余空闲: chunk.freeBytes() bytes); }}运行上述代码输出类似初始空闲字节: 65536 bytes分配16KB偏移量: 0剩余空闲: 49152 bytes分配8KB偏移量: 16384剩余空闲: 40960 bytes释放16KB后空闲: 57344 bytes分配32KB偏移量: 0剩余空闲: 24576 bytes注意偏移量计算依赖于二叉树的实现实际偏移量可能因节点分配顺序而异。上述输出展示了分配和释放的基本流程。### 深入剖析伙伴算法的优势与局限伙伴算法通过二叉树结构实现了内存块的快速分配和合并。其优势在于-高效查找从根节点向下搜索时间复杂度O(log N)N为Page数量。-低碎片合并时只合并大小相同的相邻块外部碎片较少。但伙伴算法也有局限-内部碎片请求大小必须对齐到Page例如1字节请求也占用8KB。-合并限制只能合并大小相同且地址连续的块可能导致部分内存无法回收。Netty在此基础上进行了优化例如引入Subpage处理小内存以及使用PoolThreadCache减少锁竞争。### 总结本文从零开始实现了MyNetty的Normal规格池化内存分配核心是二叉伙伴算法。我们设计了PoolChunk类包含二叉树初始化、分配和释放逻辑并提供了可运行的代码示例。通过这个简易实现我们可以理解Netty内存池化的基本原理预分配大块内存通过算法高效管理子块减少系统调用。尽管MyNetty简化了很多细节如线程缓存、Subpage、直接内存使用但它清晰地展示了Normal分配的骨架。在实际Netty中还结合了内存回收、缓存热路径等高级特性进一步提升了性能。通过这个系列的学习读者可以逐步掌握高性能网络框架的内存管理精髓。

相关新闻

最新新闻

3个步骤掌握Neat Bookmarks:Chrome浏览器树形书签管理插件专业指南

3个步骤掌握Neat Bookmarks:Chrome浏览器树形书签管理插件专业指南

3个步骤掌握Neat Bookmarks:Chrome浏览器树形书签管理插件专业指南 【免费下载链接】neat-bookmarks A neat bookmarks tree popup extension for Chrome [DISCONTINUED] 项目地址: https://gitcode.com/gh_mirrors/ne/neat-bookmarks 你是否经常在杂乱的书签…

2026/7/26 19:58:12
5分钟视频修复终极指南:用untrunc拯救你的珍贵记忆

5分钟视频修复终极指南:用untrunc拯救你的珍贵记忆

5分钟视频修复终极指南:用untrunc拯救你的珍贵记忆 【免费下载链接】untrunc Restore a truncated mp4/mov. Improved version of ponchio/untrunc 项目地址: https://gitcode.com/gh_mirrors/un/untrunc 你是否曾经历过这样的绝望时刻?婚礼视频突…

2026/7/26 19:58:12
从浪费到精准:scikit-uplift如何让你的营销ROI提升300%的终极指南

从浪费到精准:scikit-uplift如何让你的营销ROI提升300%的终极指南

从浪费到精准:scikit-uplift如何让你的营销ROI提升300%的终极指南 【免费下载链接】scikit-uplift :exclamation: uplift modeling in scikit-learn style in python :snake: 项目地址: https://gitcode.com/gh_mirrors/sc/scikit-uplift 你是否曾困惑于为什…

2026/7/26 19:58:12
learn-rust-101学习路径:从新手到Rust专家的9个阶段

learn-rust-101学习路径:从新手到Rust专家的9个阶段

learn-rust-101学习路径:从新手到Rust专家的9个阶段 【免费下载链接】learn-rust-101 A guide to aid you in your journey of becoming a Rustacean (Rust developer). 项目地址: https://gitcode.com/gh_mirrors/le/learn-rust-101 learn-rust-101是一份全…

2026/7/26 19:58:12
终极指南:XCOM 2 AML启动器 - 你的模组管理革命

终极指南:XCOM 2 AML启动器 - 你的模组管理革命

终极指南:XCOM 2 AML启动器 - 你的模组管理革命 【免费下载链接】xcom2-launcher The Alternative Mod Launcher (AML) is a replacement for the default game launchers from XCOM 2 and XCOM Chimera Squad. 项目地址: https://gitcode.com/gh_mirrors/xc/xcom…

2026/7/26 19:58:12
如何用MarkDownload解决网页内容整理难题?从信息过载到有序知识库的完整方案

如何用MarkDownload解决网页内容整理难题?从信息过载到有序知识库的完整方案

如何用MarkDownload解决网页内容整理难题?从信息过载到有序知识库的完整方案 【免费下载链接】markdownload A Firefox and Google Chrome extension to clip websites and download them into a readable markdown file. 项目地址: https://gitcode.com/gh_mirro…

2026/7/26 19:53:11

月新闻