网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
当然,要想排查内存泄漏,首先我们得确认程序有没有内存泄漏。你可以使用诸如nmon
之类的监控工具持续观察某个程序内存的使用情况,看它有没有持续上涨。如果你没有安装nmon
,也可以使用系统命令去查看某个进程的内存占用情况:
svmon -P pid -o summary=basic,unit=MB
其中summary
是摘要的意思,表示要显示哪些信息,可选项有 basic | longreal | ame | longame
, 默认是basic
,如果你想了解该参数具体代表什么含义,可以参考:svmon Command - IBM Documentation,由于这个参数在本文不是重点,我们使用basic
就行了。
bash-5.0# svmon -P 7995550 -O summary=basic,unit=MB
Unit: MB
-------------------------------------------------------------------------------
Pid Command Inuse Pin Pgsp Virtual
7995550 flow 104.70 31.7 0 97.9
如果程序因为内存泄漏造成了核心转储,也就是所谓的coredump
,那么dbx
工具就派上用场了。dbx
对于调试coredump
文件,就像Linux
上使用gdb
一样丝滑。由于dbx
工具基本为Unix
系统自带,因此不仅适用于开发调试,在生产环境也有大大的用武之地。
通过dbx
的帮助信息,可以大致了解dbx
的使用方法:
bash-5.0# dbx -h
dbx: fatal error: DBX Startup Options:
dbx [-a ProcessID] [-B DebugFile] [-c CommandFile] [-I Directory]
[ -E DebugEnvironment ] [-p [OldPath=NewPath:... | File]] [-v]
[-u] [-x] [-F] [-L] [-r] [-C CoreFile | ObjectFile [CoreFile]]
-a ProcessID Attach to specified process
-c CommandFile Run dbx subcommands in specified file first
-I Directory Include Directory in list of directories
searched for source files
-C CoreFile Allow to analyze core dump without ObjectFile
-p OldPath=NewPath Substitute library path for core examination
or when attaching to a process
File Read library path substitutions for core
examination or when attaching to a process
from File
-E DebugEnvironment Specifies the environment variable for the
debug program
-B DebugFile Specify an alternate debug file on startup
-v Relax core file validity checking
-u Prepend file name symbols with an '@'
-x Strip postfix '\_' from FORTRAN symbols
-F Read all symbols at start-up time
-L Keep linkage symbols
-r Run object file immediately
dbx
的很多用法和gdb
类似,因此,有gdb
调试基础的人来说使用起来可能比较得心应手。这也是本文所使用的的主要的调试手段。
为了更好地演示dbx
是如何工作的,我在这里准备 了一个示例代码:
//leak.c
#include<string.h>
#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
void func1();
void func2();
void main() {
while(1) func1();
}
void func1() {
func2();
}
void func2() {
char \*str;
str=(char \*) malloc(1024\*1024);
strcpy(str,"testing");
}
上面是一段非常简单的代码,我们在main
函数里不断地调用func1
函数 ,又在func1
函数里调用func2
函数 ,每次调用func2
函数,都会申请1MB
空间大小的内存,该内存并没有释放 ,也就是说,只要main
不终止,就会一直申请下去。
我们运行该段程序,很快,程序就coredump
了。
bash-5.0# ./leak
Segmentation fault (core dumped)
如果你的程序没有出现coredump
,那很有可能是ulimit
没有设置,需要设置ulimit -c
参数,它代表的是生成corefile
的最大size
:
ulimit -c unlimited
如果一切设置正确的话,你会在当前目录得到一个core
文件。我们可以使用dbx
对它进行调试,使用命令为:
dbx process corefile
运行上述命令后 ,可以得到如下信息:
bash-5.0# dbx ./leak core
Type 'help' for help.
[using memory image in core]
reading symbolic information ...internal error: unexpected value 120 at line 5176 in file stabstring.c
internal error: 1283-228 expected char ',', found 's\_\_LC\_locale:,768,32;\_\_meth\_ptr:92,800,32;\_\_data\_ptr:92,832,32;;'
internal error: 1283-228 expected char ',', found '\_\_LC\_locale:,768,32;\_\_meth\_ptr:92,800,32;\_\_data\_ptr:92,832,32;;'
internal error: 1283-228 expected char ';', found '\_LC\_locale:,768,32;\_\_meth\_ptr:92,800,32;\_\_data\_ptr:92,832,32;;'
internal error: unexpected value 44 at line 5176 in file stabstring.c
internal error: 1283-228 expected char ',', found '768,32;\_\_meth\_ptr:92,800,32;\_\_data\_ptr:92,832,32;;'
internal error: unexpected value 120 at line 5176 in file stabstring.c
internal error: unexpected value 120 at line 5176 in file stabstring.c
internal error: 1283-228 expected char ',', found 's\_LC\_locale\_objhdl:,64,32;;'
internal error: 1283-228 expected char ',', found '\_LC\_locale\_objhdl:,64,32;;'
internal error: 1283-228 expected char ';', found 'LC\_locale\_objhdl:,64,32;;'
internal error: unexpected value 44 at line 5176 in file stabstring.c
internal error: 1283-228 expected char ',', found '64,32;;'
internal error: unexpected value 120 at line 5176 in file stabstring.c
internal error: unexpected value 120 at line 5176 in file stabstring.c
internal error: unexpected value 120 at line 5176 in file stabstring.c
internal error: unexpected value 120 at line 5176 in file stabstring.c
internal error: unexpected value 120 at line 5176 in file stabstring.c
internal error: unexpected value 120 at line 5176 in file stabstring.c
internal error: unexpected value 120 at line 5176 in file stabstring.c
Segmentation fault in func2 at line 20 in file "leak.c"
20 strcpy(str,"testing");
(dbx) where
func2(), line 20 in "leak.c"
func1(), line 14 in "leak.c"
main(), line 10 in "leak.c"
(dbx)
以上信息已经报告了出现coredump
的位置。位于leak.c
的第20
行,func2
函数中,strcpy(str,"testing")
这一句代码。你可能仍然不理解,strcpy
为什么会造成核心转储,我们继续往下调试:
(dbx) p str
(nil)
当我们打印str
的值的时候,得到的是nil
,也就是 说 ,在19
行,执行str=(char *) malloc(1024*1024);
申请内存的时候,没有成功,为了验证这一点,我们将原始代码修改一下:
#include<string.h>
#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
void func1();
void func2();
static int mem_size = 0;
void main() {
while(1) func1();
}
void func1() {
func2();
}
void func2() {
char \*str;
int size = 1024\*1024;
str=(char \*) malloc(size);
if (str == NULL) {
printf("malloc %d bytes memory failed, total malloc size is %d bytes\n", size, mem_size);
exit(-1);
} else {
mem_size += size;
}
strcpy(str,"testing");
}
执行后出现如下结果:
bash-5.0# ./leak
malloc 1048576 bytes memory failed, total malloc size is 267386880 bytes
bash-5.0#
可见,确实是申请内存失败了。同时,我们也注意到total memory
是267386880
,这个大小正好是255M
。也就是说,前255M
内存申请都成功了,在第256
次申请的时候失败了。
这个256M
的限制其实 和AIX
系统 上32
位应用程序的数据段大小有关。在AIX
上,进程数据段大小主要通过LDR_CNTRL
环境变量设置,而LDR_CNTRL
这个环境变量的值,往往是和MAXDATA
值进行绑定的 。如果你想了解更详细的内容,请参考:进程内存大小限制 (ibm.com)。
那么 ,为什么是256M
呢 ?我们回到最原始的代码版本。仍然使用dbx
打开core
文件, 使用proc rlimit
可以看到ulimit
的限制值。该参数看到的往往和直接在命令行输入ulimit -a
看到的一致,不过这种方式展示的是进程的更详细的信息。
(dbx) proc rlimit
rlimit name: rlimit_cur rlimit_max (units)
RLIMIT_CPU: (unlimited) (unlimited) sec
RLIMIT_FSIZE: 1073741312 1073741312 bytes
RLIMIT_DATA: 2147483648 (unlimited) bytes
RLIMIT_STACK: 33554432 4294967296 bytes
RLIMIT_CORE: (unlimited) (unlimited) bytes
RLIMIT_RSS: 33554432 (unlimited) bytes
RLIMIT_AS: (unlimited) (unlimited) bytes
RLIMIT_NOFILE: 2000 (unlimited) descriptors
RLIMIT_THREADS: (unlimited) (unlimited) per process
RLIMIT_NPROC: (unlimited) (unlimited) per user
(dbx)
可以看到 ,RLIMIT_DATA
最大值为unlimited
,这说明可用堆内存是没有被限制的。我们可以在dbx
外面,使用dump
命令查看MAXDATA
值:
bash-5.0# dump -Xany -ov ./leak
./leak:
***Object Module Header***
# Sections Symbol Ptr # Symbols Opt Hdr Len Flags
5 0x0000c9ee 1532 72 0x1002
Flags=( EXEC DYNLOAD DEP_SYSTEM )
Timestamp = "Mar 21 13:49:17 2022"
Magic = 0x1df (32-bit XCOFF)
***Optional Header***
Tsize Dsize Bsize Tstart Dstart
0x00000d4d 0x0000042b 0x00000218 0x10000150 0x20000e9d
SNloader SNentry SNtext SNtoc SNdata
0x0004 0x0002 0x0001 0x0002 0x0002
TXTalign DATAalign TOC vstamp entry
0x0005 0x0004 0x20001244 0x0001 0x200011f4
maxSTACK maxDATA SNbss magic modtype
0x00000000 0x00000000 0x0003 0x010b 1L
可以看到 ,最后一行 ,maxDATA
显示是0x00000000
,对于32
位应用来说,0x00000000
是一个默认设置,它和0x10000000
是等价的,代表1*28大小的内存,即256M
,如果超过这个数值,就会出现核心转储。
作为示例,我们不妨将其调大点,设置成0x20000000
,也即512M
。
bash-5.0# LDR\_CNTRL=MAXDATA=0x20000000 ./leak
Segmentation fault (core dumped)
它同样会发生 coredump
,我们主要关心一下它所能申请的内存大小。这次我们直接进入dbx
去查看。
(dbx) malloc
The following options are enabled:
Implementation Algorithm........ Default Allocator (Yorktown)
Statistical Report on the Malloc Subsystem:
Heap 0
heap lock held by................ UNLOCKED
bytes acquired from sbrk()....... 535895568
bytes in the freespace tree...... 65056
bytes held by the user........... 535830512
allocations currently active..... 511
allocations since process start.. 511
The Process Heap
Initial process brk value........ 0x300014e0
current process brk value........ 0x4ff132f0
sbrk()s called by malloc......... 481
(dbx)
我们 重点关注bytes held by the user
,它代表由用户申请但没有被释放的内存,可以看到其大小为535830512
,大约是511M
多一点。这是符合我们的预期的。
这种方式其实比使用where
命令更能确定是否是内存泄漏问题造成的coredump
,因为对于一些比较复杂的程序来说 ,有可能的确正常的业务处理就要占用超过256M
内存,这时候就无法定位是正常业务内存申请导致的coredump
,还是内存泄漏导致的。
而malloc
命令很明显就是查看的malloc
申请的堆内存大小, bytes held by the user
的意思也很直观。但这种查看手段无法定位是哪一行代码出现了问题。我们当然可以两种手段结合起来看,这里我们提供另外一种调试思路。即使用MALLOCDEBUG
来记录内存申请的相关 信息。
使用也很简单:
export MALLOCDEBUG=log:extended,stack_depth:20
<start process>
unset MALLOCDEBUG
extended
参数提供了一些额外的信息,比如分配的时间、线程ID等,在多线程调试时很有用。但一般是非必需的。stack_depth
代表的是调用栈的深度,如果程序调用的函数层数比较多的话,那么可以设置多一点,可以动态调整。一般来说,20
已经够用了。
bash-5.0# export MALLOCDEBUG=log:extended,stack\_depth:20
bash-5.0# ./leak
Segmentation fault (core dumped)
bash-5.0# unset MALLOCDEBUG
执行完后,使用dbx
打开core
文件, 输入malloc allocation
,可以看到比较详细的信息:
(dbx) malloc allocation
Allocations Held by the Process:
ADDRESS SIZE HEAP PID PTHREAD_T CLOCKTIME SEQ STACK TRACEBACK
0x200204e8 1048576 0 2294034 0x00000000 1647844961 0 0xd01f27b4 malloc_common_debugging
0xd01288ec init_malloc
0xd012a234 malloc
0x10000558 func2
0x1000050c func1
0x100004d8 main
0x100001bc __start
0x201204f8 1048576 0 2294034 0x00000000 1647844961 1 0xd01f27b4 malloc_common_debugging
0x10000558 func2
0x1000050c func1
0x100004d8 main
0x100001bc __start
0x20220508 1048576 0 2294034 0x00000000 1647844961 2 0xd01f27b4 malloc_common_debugging
0x10000558 func2
0x1000050c func1
0x100004d8 main
0x100001bc __start
0x20320518 1048576 0 2294034 0x00000000 1647844961 3 0xd01f27b4 malloc_common_debugging
0x10000558 func2
0x1000050c func1
0x100004d8 main
0x100001bc __start
...more
以上信息大部分是重复的,我们可以选取其中一个查看,得到的有用信息是,malloc
申请内存是在执行main
函数时,每次调用func1
函数,然后在func1
里调用func2
函数,在 func2
里进行的内存分配,我们甚至能看到分配的内存大小,为1048576
,即1MB
。
从这些信息 ,我们 大致就能定位 出,内存泄漏很可能出现在func2
函数中。因此,我们只需要排查func2
函数中malloc
的内存是否及时释放就行了。
当然,上面这个例子过于简单,因为只有这一个地方使用了malloc
申请内存,因此定位起来还是非常快的 。但实际的企业级代码中 ,调用malloc
的地方可能有很多,不可能非常方便地找到到底是哪个地方申请 的内存出现了泄漏。如下面的程序:
//leak2.c
#include<string.h>
#include<stdlib.h>
void func1();
void func2();
void main() {
char \*a,\*b,\*c,\*d;
a = (char \*) malloc (1024\*1024);
b = (char \*) calloc (4, 64);
c = (char \*) malloc (1024\*1024\*3);
d = (char \*) malloc (256);
d = (char \*) realloc(d, 16);
while(1) func1();
free(a);
free(b);
free(c);
free(d);
}
void func1() {
char \*e;
e = calloc(1, 1024\*1024);
func2();
free(e);
}
void func2() {
char \*str, \*p;
p = (char \*) malloc(256);
str=(char \*) malloc(1024\*1024);
strcpy(str,"testing");
free(p);
}
以上这个程序就稍微复杂一点,不仅在多处都有malloc
的调用,而且还有realloc
以及calloc
的调用。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
c(256);
str=(char *) malloc(1024*1024);
strcpy(str,“testing”);
free§;
}
以上这个程序就稍微复杂一点,不仅在多处都有`malloc`的调用,而且还有`realloc`以及`calloc`的调用。
[外链图片转存中...(img-VJpITFPD-1715759831303)]
[外链图片转存中...(img-cFP5RuHV-1715759831303)]
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**
**[如果你需要这些资料,可以戳这里获取](https://bbs.youkuaiyun.com/topics/618668825)**