Nginx内存池通过“不回收小块+整批销毁”绕开碎片,按请求/连接绑定生命周期,线性分配小块、大块走large链表,复用链表节点与静态缓冲,并严控第三方模块隐式堆分配。

nginx 内存池不是靠“精细回收”来防碎片,而是用“不回收小块 + 整批销毁”直接绕开碎片产生的前提。
按请求/连接粒度绑定生命周期,消灭释放顺序问题
每个 HTTP 请求(ngx_http_request_t)或 TCP 连接(ngx_connection_t)都拥有专属内存池,由 ngx_create_pool() 创建,随请求结束或连接关闭自动调用 ngx_destroy_pool() 彻底释放。这种“归属即销毁”的模型,让所有分配的内存块在同一个时间点被整体归还——没有单个 free 调用,也就没有因释放顺序错乱、大小混杂、跨线程竞争导致的堆碎片。
常见错误包括:
- 把本该挂载在 c->pool(连接级)的 SSL 缓存误放进 r->pool(请求级),结果每次请求都重复分配,连接复用时无法复用缓存
- keepalive 场景下,一个连接承载多个请求,若大缓冲区未及时归还或误用 request pool 存储长期数据,池会持续膨胀
线性偏移分配 + 大小分流,避开 malloc 元数据与对齐浪费
小块内存(≤4095 字节,默认阈值)在线性内存块中仅靠移动 last 指针完成分配,无头信息、无对齐间隙、无锁、O(1);大块内存则走独立的 large 链表,由 malloc 单独申请,避免撑爆主池或造成内部碎片。
这种设计天然规避了 glibc ptmalloc 的三重开销:
- 每次 malloc 需维护空闲链表、分割合并块、加锁竞争
- 高频分配释放后,空闲块细碎不连续,valgrind --tool=massif 显示大量锯齿状 heap 分配峰
- 多线程下触发 mmap/munmap,引发页表抖动与 RSS 持续上涨但不回落
复用链表节点与静态缓冲,压缩 per-request 分配频次
HTTP 处理中大量使用 ngx_buf_t 和 ngx_chain_t,反复新建/销毁是碎片常见源头。
有效做法有:
- 用 ngx_chain_get_free_buf() 复用空闲 chain 节点,而非每次都调 ngx_alloc_chain_link()
- 固定长度协议头(如 HTTP/1.1 200 OK、Content-Type: text/plain)采用模块级预分配池或静态 buffer,避免 per-request 分配
- 启用 sendfile 或 directio 时,数据直通内核 socket,绕过用户态 buffer 拷贝,天然减少中间分配压力
严控第三方模块与异步回调中的隐式堆分配
第三方模块(如 Lua-nginx-module、headers-more)可能在逻辑中调用 strdup、asprintf 或触发 Lua GC,这些走 libc 堆,脱离 pool 管控。
必须检查并约束:
- 源码中是否含 malloc/strdup/asprintf 等调用;优先选用提供 pool-aware 接口的版本(如 ngx.shared.DICT 替代 Lua table 缓存)
- Lua 脚本中限制 string.format、table.concat 频次,改用预分配 buffer + string.sub 拼接
- 定时器回调(ngx_add_timer)或异步事件中绝不能误用 r->pool——请求已销毁,池内存已 free,继续 ngx_palloc 就是写入野地址
不复杂但容易忽略:内存池的威力不在分配快,而在它把“内存管理”从运行时搬到了生命周期边界。只要所有临时数据严格绑定到某个池的生命周期上,碎片就无处滋生。











