本页目录

malloc 内部: ptmalloc2

覆盖: ptmalloc2 (glibc) → arenas/bins → mmap threshold → tcmalloc / jemalloc 对比 → 调试方法 适用: glibc 2.x

概述

glibc 的 malloc 基于 ptmalloc2 (pthread malloc v2),是 dlmalloc 的多线程改进版。核心思想:每个线程有自己的分配 arena,减少锁竞争;小分配从 bins(空闲链表)中取,大分配直接 mmap。理解内部机制对排查"malloc 慢"、"内存碎片"、"RSS 远大于实际使用量"至关重要。

Arena: 线程独立的堆

dlmalloc (单线程): 1 个堆, 1 把锁
ptmalloc2 (多线程): 多个 arena

arena (分配区):
  主 arena (main_arena): 用 brk/sbrk 扩展 (位于 [heap] 区域)
  子 arena:             用 mmap 分配 (位于 mmap 区域, 匿名)

线程分配时:
  1. 尝试用自己上次用的 arena (TSD 缓存的)
  2. 如果该 arena 被其他线程持有 → 尝试下一个 arena
  3. 如果所有 arena 都有竞争 → 创建新的 arena (最多 8×cpu)

策略: 用 arena 数量换取无锁并行 → 适合多线程分配密集的程序
缺点: arena 太多 → 内存碎片 (每个 arena 有自己的 top chunk)

Bins: 空闲链表

ptmalloc2 用 bins 管理已释放的空闲块:

Fastbins (LIFO, 单链表, 不合并):
  用于极小释放 (16~64 bytes, 取决于 MAX_FAST)
  释放时: 入 fastbin
  分配时: 从 fastbin 取 (匹配则用, 不匹配→合并)

Small bins (FIFO, 循环双向链表):
  大小: 32~1024 bytes (64 bins, 每 bin 8 bytes 步进)
  相邻空闲块自动合并

Large bins (FIFO + 最佳匹配):
  大小: 1024+ bytes (63 bins, 每 bin 不等宽步进)
  Best fit: 找 ≥size 的最小空闲块

Unsorted bin (缓存层):
  新释放的块先放这里 (LIFO)
  分配时遍历 → 精确匹配? 返回 : 放入对应 small/large bin

分配流程

flowchart TD
    START["malloc(size)"]

    START --> THRESH{"size &lt; 128KB<br/>(M_MMAP_THRESHOLD)?"}

    THRESH -->|"是 → bins 路径"| FAST{"size &lt; MAX_FAST<br/>(~64B)?"}
    THRESH -->|"否 → 大分配"| MMAP["mmap(anonymous)<br/>独立映射<br/><br/>free 时 munmap<br/>fork 友好: 不 COW"]

    FAST -->|"是"| FB["查 fastbins<br/>(LIFO, 按 size index)"]
    FAST -->|"否"| SMALL{"size &lt; 1024?"}

    SMALL -->|"是"| SB["查 small bins<br/>(FIFO, 双向链表)"]
    SMALL -->|"否"| LARGE["① 查 unsorted bin<br/>② 匹配? 返回 : 放入 large bin<br/>③ 查 large bin (best fit)<br/>④ 都不够 → 从 top chunk 切"]

    FB --> OK1["✅ 返回"]
    SB --> OK2["✅ 返回"]
    LARGE --> OK3["✅ 返回"]
    MMAP --> OK4["✅ 返回"]

    FB -.->|"未命中"| SMALL
    SB -.->|"未命中"| LARGE
    LARGE -.->|"top chunk 也不够"| FALLBACK["扩展 arena<br/>sbrk (主) / mmap (子)<br/>重试分配"]
    FALLBACK -.->|"仍失败"| FAIL["❌ ENOMEM<br/>返回 NULL"]

    classDef start fill:#e3f2fd,stroke:#1565c0
    classDef decision fill:#fff3e0,stroke:#ef6c00
    classDef path fill:#f3e5f5,stroke:#7b1fa2
    classDef ok fill:#e8f5e9,stroke:#2e7d32
    classDef fail fill:#ffebee,stroke:#c62828
    class START start
    class THRESH,FAST,SMALL decision
    class FB,SB,LARGE,MMAP,FALLBACK path
    class OK1,OK2,OK3,OK4 ok
    class FAIL fail

Top Chunk

每个 arena 有一个 top chunk (堆顶的剩余空间):
  所有 bin 都不够 → 从 top chunk 切 → 扩展 top chunk (sbrk/mmap)
  释放的块在 top chunk 旁边? → 合并回 top chunk

mmap Threshold

// 大分配策略:
#define DEFAULT_MMAP_THRESHOLD (128 * 1024)  // 128KB

// 动态调整: 每次 free 大块 → threshold 可能减小
//   → mallopt(M_MMAP_THRESHOLD, value) 可以固定

// 为什么大分配用 mmap?
//   1. 释放后立即 munmap → 还给内核 → RSS 降低
//   2. 独立映射 → 不会碎片化堆
//   3. fork 友好: 子进程改自己的 (不是 COW)

多线程陷阱

虚假共享 (False Sharing)

两个线程频繁分配/释放 → 可能共享同一个 arena
  → arena 的 mutex → 竞争 → 锁开销

缓解:
  malloc_trim(0): 强制所有 arena 释放空闲空间给内核
  MALLOC_ARENA_MAX: 环境变量限制最大 arena 数

内存碎片

频繁分配+释放不同大小 → bins 中的空闲块无法重用:
  例: 分配 512B → free → 分配 1024B → 512B 块太小 → 从 top chunk 切
  → heap 逐渐变大但实际使用量很小 → RSS 高

检测:
  malloc_stats() 或 malloc_info(0, stderr) → 看 system bytes vs in use

tcmalloc / jemalloc 对比

ptmalloc2 (glibc)tcmalloc (Google)jemalloc (FreeBSD)
线程模型per-thread arena (mutex)per-thread cache (无锁)per-thread cache (tcache)
碎片中等 (bins 机制)低 (大小分类更细)低 (extent-based)
大分配mmap, 128KB+mmap, 256KB+mmap, 2MB+
内存分析malloc_stats()HeapProfiler (CPU/mem)jeprof (CPU/mem)
适合通用多线程服务多线程服务, 低碎片

换用非 glibc allocator

# 运行时替换 (不重编译):
LD_PRELOAD=/usr/lib/libtcmalloc.so ./my_program
LD_PRELOAD=/usr/lib/libjemalloc.so ./my_program

调试与调优

# malloc 统计
MALLOC_TRACE=/tmp/mtrace.log ./my_program  # 记录所有 malloc/free
mtrace ./my_program /tmp/mtrace.log          # 分析 leak

# 运行时参数
MALLOC_ARENA_MAX=2 ./my_program             # 限制 arena 数
MALLOC_MMAP_THRESHOLD_=65536 ./my_program   # 调低 mmap 阈值

# valgrind 检查
valgrind --leak-check=full ./my_program

# ASAN (编译时)
gcc -fsanitize=address -g -o prog prog.c

# 查看内存分配统计 (glibc)
#include <malloc.h>
malloc_info(0, stderr);   # XML 格式的详细统计
malloc_stats();           # 简单的 stderr 统计

# /proc 观察
cat /proc/<pid>/status | grep Vm

参考

  • glibc 源码⁠: malloc/malloc.c (~5000行, ptmalloc2 的完整实现)
  • 文档⁠: man mallopt, man malloc_info
  • LWN: "The design of glibc malloc", "Hoard, tcmalloc, and jemalloc"

关键词: ptmalloc2, arena, bins, fastbin, mmap threshold, tcmalloc, jemalloc, malloc_stats, ASAN