最近引擎版本上线,测试人员发现一个"奇怪问题",引擎实例不停的创建和销毁过程中,内存增长过快,看起来很像是内存泄漏,但是工具valgrind跑出来,也没发现代码的泄漏点,跑了很多次循环后,内存增加到一定后,就表现平稳,所以近期抽点时间,系统的学习下linux系统内存管理方式
本次从以下四个问题出发:
1.Linux虚拟地址如何分布? 我们常常说的 32位和64为有啥不同? 虚拟地址和物理地址区别?
2. 如何分配内存, malloc ?
3.malloc 是在虚拟地址 ? 物理地址分配内存,程序需要多少内存,就真的分配多大物理内存么?
4.进程虚拟地址空间使用情况?
问题一:
(1).只读段:该部分空间 只能读,不可写,包括代码段,(C常量字符串 和 宏定义定义的常量)
(2).数据段: 保存全局变量,静态变量空间
(3).堆空间: new/delete ,malloc/free 内存大部分在此创建,堆顶位置可通过函数brk,和sbrk进行动态申请,
sbrk( >0 的size)映射一个4k的page,并且分配一个空间,得到没有映射的虚拟地址,使用brk分配空间,
brk一般配合这sbrk做申请内存和释放内存使用,使用sbrk得到地址后,使用brk做偏移。
(4).文件映射区域:如动态库,共享内存等映射物理空间内存,一般是mmap函数分配的虚拟地址空间,内存映射函数mmap,负责把文件内容映射到进程虚拟内存空间,通过对这段内存读取和修改,来实现对文件的读取和修改
(5).栈: 用于维护函数调用的上下文空间,一般是8M ,ulimit -s查看
(6).内核虚拟空间,用户代码不可见内存区域
32位的操作系统有4G地址空间,其中 0x8048000 ~ 0xbfffffff 是用户空间,0xc000000 ~ 0xfffffff
是内核空间,包括内核代码和数据,进程相关的数据结构 页表,内核栈,指针%esp指针在栈顶,往低地址方向移动。堆内存控制函数
brk、sbrk控制堆顶向高地址方向变化。
64位操作系统地址空间不是2的32次方,也不是2的64次方。一般是2的48次方(256TB寻址空间),实际不需要2的64次方这么大的寻址空间。
问题二(malloc 是如何分配内存):
malloc是glibc中内存分配函数,也是最常用的动态内存分配函数,malloc是配合free进行释放,否则导致内存泄漏,以下为最常用的流程:
a. 分配内存< 128k,调用sbrk(),将堆顶指针指向高地址,获取虚拟空间
b.分配内存> 128k,调用mmap(),在文件映射区域中分配匿名虚拟空间
128k 是 glibc 的默认配置,可通过函数 mallopt
问题三(malloc 分配虚拟内存和物理内存区别):
1.malloc 分配的内存是虚拟地址空间的内存,实际使用的物理地址的内存是 虚拟地址空间使用进程页表进行映射,如下实例:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <malloc.h>
char ps_cmd[1024];
void print_info(char* var_name,char* var_ptr,size_t size_in_kb)
{
printf("Address of %s(%luk) 0x%lx, now heap top is 0x%lx\n",
var_name, size_in_kb, var_ptr, sbrk(0));
system(ps_cmd);
}
int main(int argc, char** argv)
{
char *non_set_var, *set_1k_var, *set_5k_var, *set_7k_var;
pid_t pid;
pid = getpid();
sprintf(ps_cmd, "ps aux | grep %lu | grep -v grep", pid);
non_set_var = (char*)malloc(32*1024);
print_info("non_set_var", non_set_var, 32);
set_1k_var = malloc(64*1024);
memset(set_1k_var, 0, 1024);
print_info("set_1k_var", set_1k_var, 64);
set_5k_var =(char*)malloc(127*1024);
memset(set_5k_var, 0, 5*1024);
print_info("set_5k_var", set_5k_var, 127);
set_7k_var = (char*)malloc(64*1024);
memset(set_1k_var, 0, 7*1024);
print_info("set_7k_var", set_7k_var, 64);
return 0;
}
总结(ps aux 获取当前进程的VSZ(虚拟内存),RSS(物理内存)大小):
1. VSZ并不是每次malloc都是增长,主要是看堆顶指针变化没有,因为malloc后 堆顶内存可以重用,不需要重新移动指针来开辟内存
2.VSZ发生变化时,变化的大小基本与程序分配的大小基本差不多
3.malloc分配的内存 并不是立即分配实际的物理内存,只有使用实际内存时候 才会分配物理内存
4.每个物理内存页面大小是4k,实际占用物理内存一定是4k的倍数
所以: 并不是malloc后马上就占用实际的物理内存,RSS不是立即增加,而是使用时发现虚拟内存中对应的物理页面未分配,产生缺页中断,才会分配实际的物理内存。