ngx_pool_t通过整批创建与销毁彻底规避内存碎片,而非回收小块内存;其核心是将内存管理移至生命周期边界,仅用指针偏移分配,O(1)无锁,大块走large链表,结束时整池释放。

直接说结论:ngx_pool_t 不是靠“回收小块内存”来降低碎片,而是压根不回收——整批创建、整批销毁,彻底绕过碎片产生的根本条件。
为什么 malloc/free 在高并发下必然产生碎片
每个 HTTP 请求平均分配几十到上百次小内存(headers、临时 buf、变量结构体),每次 malloc 都要维护元数据、做空闲链表合并/分割;高频申请释放后,空闲块变得细碎且不连续。glibc 的 ptmalloc 在多线程下还会加锁、触发 mmap/munmap,进一步放大延迟和页表抖动。
常见错误现象:malloc 耗时突增、RSS 持续上涨但 free 后不回落、valgrind --tool=massif 显示大量 heap allocation 碎片峰。
- nginx 的请求生命周期极短(毫秒级),根本不适合走“逐块 free”的路径
- 碎片不是出现在分配时,而是出现在释放顺序不确定、大小不一、跨线程竞争时
- 传统堆管理器无法假设“这批内存会在 200ms 后全部失效”,所以不敢激进合并
ngx_pool_t 如何用“不释放”反向消灭碎片
它把“内存管理”从运行时搬到了生命周期边界:池的创建和销毁完全对齐请求或连接,中间只做指针偏移,没有释放动作。
-
ngx_palloc就是移动last指针并返回地址,O(1),无锁,不查链表 - 超过
NGX_MAX_ALLOC_FROM_POOL(默认 4095 字节)的大块走large链表,单独malloc,不污染主池线性区 - 请求结束时调用
ngx_destroy_pool(),直接free()整个 pool 内存块 + 遍历large链表逐个free(),无顺序依赖 - 池可嵌套(如子请求用
ngx_create_pool(1024)),但销毁仍按创建层级整批归还
容易被忽略的三个破坏点
即使用了 ngx_pool_t,只要踩中下面任意一点,碎片就立刻回归。
- 在定时器回调(
ngx_add_timer)或异步事件中误用r->pool:请求已销毁,池内存早已free,继续ngx_palloc就是写入野地址 - 第三方模块调用
strdup、asprintf或 Lua 的string.format:这些走 libc 堆,脱离 pool 管控,长期积累就是隐式碎片源 - 反复在循环里
ngx_palloc(r->pool, 8)分配极小结构(如 key-value pair):虽不越界,但会快速耗尽池内对齐间隙,导致后续分配被迫切新块,增大池链长度
最隐蔽的问题往往不在 ngx_palloc 本身,而在于你是否真的让所有临时数据都严格绑定到某个池的生命周期上——比如一个本该属于 c->pool(连接级)的 SSL 缓存,被错误地挂在了 r->pool(请求级)上,结果每请求都重建一次。











