内存管理速查表

freelist / 大内存分配测试 / 伪共享 padding / 内存占用分析

Posted by BY on April 25, 2026

原始笔记是几段散乱的内存相关片段:一段被压扁成一行的虚/实内存测试代码、几个分配器名字孤零零地列着、padding 的 Java/C 例子、以及 /proc/$pid/smaps 的字段表。这里按”分配器 / 大内存测试 / 伪共享 padding(Java + C)/ 内存占用分析的四个层级”分节整理,原始代码原样保留。

当前保留内容

1. freelist


(待补充:自己写一个 freelist 的最小实现,对比 ptmalloc / jemalloc / tcmalloc 在小对象上的差异。)

2. 分配大量虚存或实存(测试用)

下面两个函数是当时用来观察”虚拟内存 vs 物理内存”占用差异的小工具。原始笔记里被压成了一行,这里保持原样不展开,避免修改测试结果:

        //  大量分配虚拟内存,每次调用分配20Gvoid virtualMemoryTest(){    int sizeVm = 1 * 1024 * 1024 * 1024;    //  1GB    for(int i = 0; i < 20; i++){    //  1GB * 20        void* block = mmap(NULL, sizeVm, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);        memset(block, 1, 1);        list[index++] = block;    }}
//  大量分配物理内存,每次调用申请160Mvoid physicalMemoryTest(){    int size = 8 * 1024 * 1024;     //  8M    for (int i = 0; i < 20; i++) {  //  8M * 20        void *block = malloc(size);        memset(block, 1, size);        list[index++] = block;    }}

3. 关注的分配器与基础组件

  • leveldb
  • ptmalloc
  • jemalloc
  • tcmalloc
  • brpc 中的 task area、iobuf

(每一项都待单独写读源码笔记。)

4. 局部性原理与 padding

4.1 规避伪共享的两种思路

  1. 增大数组元素的间隔使得不同线程存取的元素位于不同 cache line,空间换时间。
  2. 在每个线程中创建全局数组各个元素的本地拷贝,然后结束后再写回全局数组。

从代码设计角度,要考虑清楚类结构中哪些变量是不变的、哪些是经常变化的、哪些变化是完全相互独立的、哪些属性一起变化。假如业务场景中下面的对象满足几个特点:

public class Data{
    long modifyTime;
    boolean flag;
    long createTime;
    char key;
    int value;
}
  • 当 value 变量改变时,modifyTime 肯定会改变
  • createTime 变量和 key 变量在创建后就不会再变化
  • flag 也经常会变化,不过与 modifyTime 和 value 变量毫无关联

当上面的对象需要由多个线程同时访问时,从 Cache 角度,当我们没有加任何措施时,Data 对象所有的变量极有可能被加载在 L1 缓存的一行 Cache Line 中。在高并发访问下会出现这种问题:

image

如上图所示,每次 value 变更时,根据 MESI 协议,对象其他 CPU 上相关的 Cache Line 全部被设置为失效。其他的处理器想要访问未变化的数据(key 和 createTime)时,必须从内存中重新拉取数据,增大了数据访问的开销。

4.2 有效的 Padding 方式(Java 示例)

正确方式是将该对象属性分组:将一起变化的放在一组,与其他无关的放一组,将不变的放到一组。这样当每次对象变化时,不会带动所有的属性重新加载缓存,提升了读取效率。在 JDK1.8 前,一般在属性间增加长整型变量来分隔每一组属性。被操作的每一组属性占的字节数加上前后填充属性所占的字节数,不小于一个 cache line 的字节数就可达到要求。

public class DataPadding{
       long a1,a2,a3,a4,a5,a6,a7,a8;//防止与前一个对象产生伪共享
       int value;
       long modifyTime;
       long b1,b2,b3,b4,b5,b6,b7,b8;//防止不相关变量伪共享;
       boolean flag;
       long c1,c2,c3,c4,c5,c6,c7,c8;//
       long createTime;
       char key;
       long d1,d2,d3,d4,d5,d6,d7,d8;//防止与下一个对象产生伪共享
}

采用上述措施后的图示:

image

4.3 C 语言 padding

在设计数据结构的时候,尽量将只读数据与读写数据分开,并尽量将同一时间访问的数据组合在一起。这样 CPU 能一次将需要的数据读入。譬如,下面的数据结构就很不好:

struct __a

{

   int id; // 不易变

   int factor;// 易变

   char name[64];// 不易变

   int value;// 易变

};

在 X86 下,可以试着修改和调整它:

#define CACHE_LINE_SIZE 64  //缓存行长度

struct __a

{

   int id; // 不易变

   char name[64];// 不易变

  char __align[CACHE_LINE_SIZE – sizeof(int)+sizeof(name) * sizeof(name[0]) %
CACHE_LINE_SIZE]

   int factor;// 易变

   int value;// 易变   char __align2[CACHE_LINE_SIZE –2* sizeof(int)%CACHE_LINE_SIZE ]
};

CACHE_LINE_SIZE – sizeof(int)+sizeof(name)*sizeof(name[0])%CACHE_LINE_SIZE 看起来不和谐,CACHE_LINE_SIZE 表示高速缓存行(64B 大小)。__align 用于显式对齐,这种方式使得结构体字节对齐的大小为缓存行的大小。

5. 内存占用分析

5.1 编译后程序各区域的大小

size -A bin

5.2 虚拟内存占用分析

image

/proc/$pid/smaps 中的字段含义:

成员名	含义
Name	进程的名称
Pid	PID。
VmPeak	进程使用的最大虚拟内存,通常情况下它等于进程的内存描述符mm 中的 total_vm.
VmSize	进程使用的虚拟内存,它等于mm->total_vm。
VmLck	进程锁住的内存,它等于mm->locked_vm,这里指使用mlock()锁住的内存。
VmPin	进程固定住的内存,它等于mm->pinned_vm,这里指使用 get_user_page()固定住的内存。
VmHWM	进程使用的最大物理内存,它通常等于进程使用的匿名页面、文件映射页面以及共享内存页面的大小总和。
VmRSS	进程使用的最大物理内存,它常常等于VmHWM,计算公式为 VmRSS= RssAnon+RssFile+RssShmem.
RssAnon	进程使用的匿名页面,通过get_mm_counter(mm,MM_ANONPAGES)获取。
RssFile	进程使用的文件映射页面,通过get_mm_counter(mm, MM_FILEPAGES)获取。
RssShmem	进程使用的共享内存页面,通过get_mm_counter(mm, MM_SHMEMPAGES)获取。
RssFile	进程使用的文件映射页面,通过get_mm_counter(mm, MM_FILEPAGES)获取 RssShmem:进程使用的共享内存页面,通过getmm_counter(mm,MM SHMEMPAGES获取。
VmData	进程私有数据段的大小,它等于 mm->data_vm。
VmStk	进程用户栈的大小,它等于mm->stack_vm。
VmExe	进程代码段的大小,通过内存描述符mm中的start_code和end_code两个成员获取。
VmLib	进程共享库的大小,通过内存描述符mm中的exec_vm和VmExe计算。
VmPTE	进程页表大小,通过内存描述符 mm 中的pgtables_bytes 成员获取。
VmSwap	进程使用的交换分区的大小,通过get_mm_counter(mm,MM_SWAPENTS)获取。
HugetlbPages	进程使用巨页的大小,通过内存描述符 mm 中的 hugetlb_usage成员获取。
https://blog.csdn.net/weixin_39247141/article/details/126273389

5.3 物理内存占用分析

image

5.4 heap 占用分析

heap_profiler(gperftools 提供)。

后续可补的方向

  • 把”分配大量虚/实内存”代码格式化展开,并配套一份 RSS / VSZ 观察脚本。
  • 用 perf c2c 复现 padding 章节中的伪共享案例,给出”前后对比”的数据。
  • 针对 ptmalloc / jemalloc / tcmalloc 各做一份”小对象 / 大对象 / 多线程”基准对比。