作者: 李凌云,张一峰(laoeyu)

概述

内存泄漏是应用软件开发过程中经常会遇到的问题,应用长期内存泄漏会占用大量操作系统内存资源,直接导致应用程序运行不稳定,严重时甚至还会影响到操作系统的正常运行。为了找到应用程序内存泄漏点,许多开发人员不得不在上千行乃至几十万行源程序中加入更多的调试代码,试图从调试信息中找到内存泄漏的根源,但通常来讲这种方法是事倍功半的。幸运的是,Solaris平台提供了好几个实用的工具,能够辅助开发人员对内存泄漏根源进行定位。笔者参考了 Sun公司官方网站上相关的英文技术文档,并认为这些对于我们中国广大的Sun技术爱好者有很好的指导作用。笔者经过消化整理后写下此文希望与大家共享。在下面的章节 中将初步讲述如何在Solaris 10操作系统下利用这些工具查找用户程序的内存泄漏点。文章各节标题如下:

1. 概述
2. 内存泄漏及危害
3. dbx
4. libumem
5. DTrace
6. libgc
7. 总结
8. 参考资料

内存泄漏及危害

如果要给内存泄漏下个定义的话,它应该属于软件程序设计的一种缺陷,该缺陷直接导致了程序在运行过程中无法释放不再需要的内存空间,从而造成内存资源浪费。具体来说,当用户程序在运行过程中需要动态获得内存时,操作系统总是从堆(heap)上分配相应的空间给应用,分配的结果是将该堆内存的起始地址通过指针返回给应用。正常情况下,应用使用完这块内存后,应通过系统调用主动通知操作系统回收这些堆内存以便重用。但是,如果由于设计缺陷导致在某些情况下程序没有主动地通知到操作系统,而后应用又失去了对这块内存的引用时,则该堆内存块将成为既不受程序控制,又不能被系统回收重用的“孤儿”内存,这便是我们所指的内存泄漏。

造成内存泄漏的设计缺陷多种多样,下面例举了部分典型的内存泄漏设计缺陷,它们都是开发人员经常会犯的毛病。

1

void foo( )
{
    char *str;
    str = (char *) malloc(32);
    strcpy(str, "hello world");
    return;
    /* str所指向的32个字节的内存没有被释放,当foo()返回时造成内存泄漏 */
}

2

void PrintCWD( )
{
    printf("cwd = %sn", getcwd(NULL, MAXPATHLEN));
    return;
/* 某些系统调用本身就会在堆上申请一块内存空间,然后将指针返回 */
/* 给调用者。比如,系统调用getcwd()就将当前的工作目录路径保存 */
/* 在一块堆内存上,然后返回给调用者。应用应该使用指针接收该类 */
/* 指针,并在用完后释放该内存。而本例中却没有将getcwd()返回 */
/* 的内存块释放,从而造成内存泄漏 */
}

3

void foo( )
{
    char *string1 = malloc(100);
    char *string2 = malloc(200);

scanf("%s", string2);
    string1 = string2; /* string1原先指向的100个字节的内存没有被释放,*/
    /* 而后又被指向string2所指的内存块,造成前面*/
    /* 100个字节的内存泄漏 */
    free(string2);
    free(string1); /* 这个free()调用会失败,因为string1指向的内存地址 */
    /* 与string2的相同,而那块内存已经被释放了 */
    return 0;
}

4

int MyFunction(int nSize)
{
    char* p= new char[nSize];
    if ( !GetStringFrom( p, nSize ) )
        return -1; /* 在异常情况下,p所指向的nSize个字节内存没有被 */
        /* 释放,造成内存泄漏 */

/* 使用p所指的内存 */
        …
      delete p;
      return 0;
}

一个原先运行正常的系统由于安装运行了用户应用程序后经常发生以下任意症状时,则应用程序有可能存在内存泄漏问题:

  • 没有特殊原因,应用程序经长时间运行后所占虚拟内存总量仍在持续性地不断增长(即使增长过程十分缓慢)。通过使用prstat可以查询进程内存使用情况。

  • 在swap设备配置和运行正常,且没有大文件占用/tmp目录的情况下,经常有进程报告“Out of Memory”错误。通过使用swap -s命令可以查询swap空间使用情况,以及使用df -k查询/tmp的使用情况。

一个有内存泄漏问题的应用程序经过长时间运行后,通常会逐渐占用大量操作系统内存。操作系统会因内存短缺而造成整体性能下降,严重时可以造成系统中其他真正需要内存的进程因得不到内存空间而无法正常运行,操作系统也会由于内存耗尽而变得不稳定。在下面的章节中,我们将介绍在Solaris 10平台上有哪些工具帮助我们分析内存泄漏问题。

dbx

Sun Studio是Sun公司推出的面向C、C++和Fortran语言编程的开发环境,目前最新版本是Sun Studio 11。它包括一个非常友好而专业的GUI集成开发环境,以及像dbx、Performance Analyzer等优秀的辅助工具。其中,dbx工具除了可以帮助开发人员进行源代码级别的跟踪调试以外,它还可以帮助开发人员查找和定位应用程序中内存泄漏的问题。令人兴奋的是,Sun Studio 11不像以前各版本那样需要购买License,它是免费下载和使用的(包括商用),有兴趣的话可以到http://developers.sun.com/prodtech/cc/downloads/index.jsp下载。

Runtime Checking

dbx中提供内存泄漏检查功能的模块被称为RTC,即Runtime Checking。它除了提供内存泄漏检查功能外,还可以进行内存访问检查和使用检查,对于发现程序中内存越界访问或者变量未初始化就访问等编程问题很有帮助。缺省情况下,dbx不启用内存泄漏检查功能,用户可以通过check -leaks命令启用它。check -leaks是一个反复切换开关,在已经启用内存泄漏检查功能的情况下再使用该命令可以关闭这个功能。使用内存泄漏检查功能不需要对程序进行重编译,它也支持在优化过的目标代码中查找内存泄漏。

使用RTC模块有一些前提要求,比如:

  • 程序必须是由Sun提供的编译器所编译生成的;

  • 程序使用动态库方式链接libc库;

  • 内存是通过libc库的malloc()、free()、realloc()或其他基于这些调用的函数进行申请和管理的;

  • 程序不能被完全strip,即符号表必须存在。strip -x命令仍可以接受。

RTC还有部分限制,比如:

  • 只能在Solaris操作系统上使用;

  • 在基于非UltraSPARC芯片的主机系统上使用时,程序的text段和数据data段不能超过8MB空间。

RTC对于内存泄漏分三种情况:

  • Memory Leak(mel),即进程中不存在任何一个指针指向某内存块,则该内存块为真正的内存泄漏块。

  • Address in Block(aib),即进程中不存在任何一个指针指向某内存块的启始位置,却存在指向该内存块中间某位置的指针。这是一个可疑的内存泄漏,即它很可能会演变为Memroy Leak,但也不排除程序设计者为了某种需要故意设计成这样的。
  • Address in Register(air),即进程代码段及数据段中没有任何一个指针指向该内存块,但在至少一个寄存器中存在相关的指针。这是一个可疑的内存泄漏。如果程序在编译时使用了优化选项,比如-O等,则编译器有可能只将内存指针保留于寄存器中,否则这会演变为真正的内存泄漏。

dbx在报告内存泄漏时会区分上述三种情况,对于可能的内存泄漏,开发人员需要自行判断是否为真正的内存泄漏。

用dbx查内存泄漏

使用 dbx检查内存泄漏是所有工具中最方便的。如果程序在编译时使用了 -g选项, dbx可以很方便地将内存泄漏点定位到源程序代码行。使用 dbx检查内存泄漏的典型过程如下:

1. 使用dbx启动被跟踪的程序。

$ dbx ./a.out

2. 用check -leaks打开内存泄漏检查开关。

(dbx) check -leaks
leaks checking - ON

3. 运行程序直至结束。当程序运行结束时,dbx会给出类似以下的内存泄漏报告。

(dbx) run
Running: a.out
(process id 26767)
Reading rtcapihook.so
Reading libdl.so.1
Reading rtcaudit.so
Reading libmapmalloc.so.1
Reading libgen.so.1
Reading libm.so.2
Reading libc_psr.so.1
Reading rtcboot.so
Reading librtc.so
RTC: Enabling Error Checking...
RTC: Running program...
Checking for memory leaks...

Actual leaks report (actual leaks: 2 total size: 43 bytes)

Total Num of Leaked Allocation call stack
Size Blocks Block
Address
========== ====== =========== =======================================
32 1 0x21198 memory_leak < main
11 1 0x21210 address_in_register < main

Possible leaks report (possible leaks: 0 total size: 0 bytes)

execution completed, exit code is 0
(dbx)

例子中报告了两个内存泄漏,分别为从 main()过程调用到 memory_leak()过程时有1次32字节的内存泄漏,以及从 main()过程调用到 address_in_register()过程时有1 次11字节的内存泄漏。为了得到具体的源代码行号,可以在运行程序前使用以下的命令将内存泄漏报告模式改为 verbose,然后重新运行程序。

(dbx) dbxenv rtc_mel_at_exit verbose
(dbx) run
Running: a.out
(process id 26768)
RTC: Enabling Error Checking...
RTC: Running program...
Checking for memory leaks...

Actual leaks report (actual leaks: 2 total size: 43 bytes)

Memory Leak (mel):
Found leaked block of size 32 bytes at address 0x21198
At time of allocation, the call stack was:
[1] memory_leak() at line 8 in "leak.c"
[2] main() at line 42 in "leak.c"

Memory Leak (mel):
Found leaked block of size 11 bytes at address 0x21210
At time of allocation, the call stack was:
[1] address_in_register() at line 35 in "leak.c"
[2] main() at line 44 in "leak.c"

Possible leaks report (possible leaks: 0 total size: 0 bytes)

execution completed, exit code is 0
(dbx)

例子中报告了内存泄漏点分别在源代码第8行和第35行。

如果要检查一个守护进程类型的服务程序是否发生内存泄漏,上述方法就不适用了,这是因为守护进程永远不会运行结束。对此,dbx提供了一个showleaks的命令可以让开发人员在任何时候查看进程内存泄漏情况。另外,守护进程一般会多次进行fork(),所以也不适合采用dbx直接进行启动。因此,对于守护进程类程序,开发人员可以通过以下方法启动,然后dbx动态挂接到已运行的进程上再进行内存泄漏检查。

1. 设定环境变量,预装librtc.so。

$ LD_AUDIT=/opt/SUNWspro/lib/rtcaudit.so; export LD_AUDIT

缺省情况下在程序启动时librtc.so不会预装入系统。这意味着即使后来dbx动态挂接上该进程后,也无法使用RTC功能。但开发人员可以通过设定LD_AUDIT环境变量,指定应用程序启动时系统预装入librtc.so。方法是:对于32位应用,将LD_AUDIT指向<Sun Studio 11安装目录>/lib/下的rtcaudit.so,对于SPARC 64位应用,须指向lib/v9下的rtcaudit.so,对于AMD 64位应用,则为lib/amd64/下的rtcaudit.so。

2. 启动守护程序,并得到进程号。注意,启动应用后应及时使用unset命令去除LD_AUDIT设置。后继命令不应使用LD_AUDIT。

$ ./mysvc
$ unset LD_AUDIT
$ pgrep mysvc
27020

3. 令dbx动态挂接上守护进程,关闭同步跟踪,并打开内存泄漏检查。可根据需要在程序合适的位置设好断点,然后继续执行程序或单步跟踪执行程序。在必要的时候利用showleaks检查内存泄漏情况。

$ dbx ./myapp 27020
Reading mysvc
Reading ld.so.1
Reading libc.so.1
Reading rtcaudit.so
Reading libmapmalloc.so.1
Reading libgen.so.1
Reading libdl.so.1
Reading libm.so.2
Reading rtcboot.so
Reading librtc.so
Attached to process 27020
stopped in _syscall6 at 0xfed3edf4
0xfed3edf4: _syscall6+0x0020: blu _cerror ! 0xfeca06a0
Current function is main
38 msize = msgrcv(msgid, &msg, sizeof(1024), 0, 0);

dbx: internal warning: set_error_limit called too early
(dbx) dbxenv mt_sync_tracking off
(dbx) check -leaks
leaks checking - ON
RTC: Enabling Error Checking...
RTC: Running program...
(dbx) stop at 38
(2) stop at "mysvc.c":38
(dbx) showleaks -v
...

(dbx) cont
...
(dbx) showleaks -a -v
...

其中,showleaks命令缺省只报告自上次报告内存泄漏后新发现的内存泄漏。如果使用-a选项,则showleaks报告所有内存泄漏。-v选项是指verbose模式,可以给出更详细的报告。跟踪完成后,可使用quit命令退出dbx。

libumem

libumem的由来是原自SunOS 5.4中的Kernel Slab Allocator。为了加速系统虚拟内存操作,Sun的工程师发明了Kernel Slab Allocator,后来也被推广到Linux操作系统。Kernel Slab Allocator通过一种object cacheing技术策略来实现高效内存的处理。在实际使用中,这种Kernel Slab Allocator内存分配器被证明非常高效,在多颗CPU上,扩展性(Scability)表现亦极佳。由于Kernel Slab Allocator是工作在kernel状态,所以相应地产生了一个在用户空间工作的内存分配器: libumem。自Solaris 9(update3)开始,Solaris就自带这个全新的内存分配器: libumem。

libumem不仅能够优化程序的内存分配,而且还提供内存分配调试,记录功能,配合mdb工具我们可以轻松观察程序内存的分配情况和内存泄漏。在使用libumem检测内存泄漏的问题的之前,我们必须了解一些在调试中libumem提供给我们信息的内存结构。

libumem工作内存结构

libumem也是使用Slab概念。Slab是Slab Allocator中一个基本内存单元:Slab是代表一个或者多个虚拟内存中的页(Page),它通常会被分割成为多个大小等同的Chunks,被成为Buffer。Buffer含有用户所使用的数据,还会有一些额外的信息,不过这个取决环境变量的设置。这些额外的信息对我们调试,检测内存泄漏非常有用。下面就是Buffer的一个基本结构:

Metadata Section

User Data Section

Redzone Section

Debug Metadata Section

指针 (4字节)

验证码 (4字节)

Buffer结构中第一个section是Metadata,主要提供内存分配的长度信息,我们这里不使用,在32位程序应用中它是8个字节。Metadata后面是存储用户数据的User data Section。接着是Redzone部分,Redzone也是8个字节。最后是Debug Metadata也是8个字节。其中前四个字节代表一个指针,指向一个umem_bufctl_audit结构,这个结构记录着内存分配时候的堆栈。该结构的定义可以在/usr/include/umem_impl.h找到。后面四个字节是校验位,可以用来和前面字节一起来判断这个buffer有没有被破坏。

libumem使用方法

1. 预加载(Preload) libumem

如果在程序中需要调试,寻找内存泄漏,需要预先加载(Preload) libumem,并且设置上环境变量:UMEM_DEBUG=default,UMEM_LOGGING=transaction,LD_PRELOAD=libumem.so.1 。

在csh中设置的例子

% (setenv UMEM_DEBUG default; setenv UMEM_LOGGING transaction; setenv LD_PRELOAD libumem.so.1; ./a.out)

在bash中设置的例子

bash-3.00$ UMEM_DEBUG=default; UMEM_LOGGING=transaction; LD_PRELOAD=libumem.so.1; ./a.out

2. 运行程序,在程序运行时使用gcore命令对目标程序的进程生成core文件。

bash-3.00$ gcore `pgrep a.out`
gcore: core.1478 dumped

3. 使用mdb命令倒入core文件。

bash-3.00$ mdb core.1478

  • ::umem_status命令查看libumem的日志功能是否打开。

    > ::umem_status
    Status: ready and active
    Concurrency: 1
    Logs: transaction=64k
    Message buffer:

  • ::findleaks命令查看是否有内存泄漏。

    > ::findleaks
    CACHE LEAKED BUFCTL CALLER
    0003d888 1 00050000 main+0xc
    --------------------------------------------------------
    Total 1 buffer, 24 bytes

  • 内存泄漏地址$<bufctl_audit,该命令会将该地址的内容以umem_bufctl_audit的结构,并且会显示内存泄漏的时候的用户堆栈。

    > 50000$<bufctl_audit
    0x50000: next addr slab
    0 49fc0 4bfb0
    0x5000c: cache timestamp thread
    3d888 23764722653000 1
    0x5001c: lastlog contents stackdepth
    2e000 0 5
    libumem.so.1`umem_cache_alloc+0x13c
    libumem.so.1`umem_alloc+0x44
    libumem.so.1`malloc+0x2c
    main+4
    _start+0x108

  • ::umalog命令查看每次内存分配的时间,地址,堆栈。

    > ::umalog
    T-0.000000000 addr=55fb8 umem_alloc_32
    libumem.so.1`umem_cache_alloc+0x13c
    libumem.so.1`umem_alloc+0x44
    libumem.so.1`malloc+0x2c
    main+0x18
    _start+0x108
    T-0.000457800 addr=49fc0 umem_alloc_24
    libumem.so.1`umem_cache_alloc+0x13c
    libumem.so.1`umem_alloc+0x44
    libumem.so.1`malloc+0x2c
    main+0xc
    _start+0x108

  • 内存地址::umem_verify可以查看内存是否被破坏,比如内存的越界操作。

    > ::umem_verify
    Cache Name Addr Cache Integrity
    umem_magazine_1 3c008 clean
    umem_magazine_3 3c1c8 clean
    umem_magazine_7 3c388 clean
    umem_magazine_15 3c548 clean
    umem_magazine_31 3c708 clean
    umem_magazine_47 3c8c8 clean
    umem_magazine_63 3ca88 clean
    umem_magazine_95 3cc48 clean
    umem_magazine_143 3ce08 clean
    umem_slab_cache 3cfc8 clean
    umem_bufctl_cache 3d188 clean
    umem_bufctl_audit_cache 3d348 clean
    umem_alloc_8 3d508 clean
    umem_alloc_16 3d6c8 clean
    umem_alloc_24 3d888 clean
    umem_alloc_32 3da48 clean
    ... snip ...

  • 内存地址::umem_log可以按CPU,线程打印出内存分配记录。

> ::umem_log
CPU ADDR BUFADDR TIMESTAMP THREAD
0 0002e064 00055fb8 10475e3dd1c98 00000001
0 0002e000 00049fc0 10475e3d62050 00000001
0003483c 00000000 0 00000000
000348a0 00000000 0 00000000

00034904 00000000 0 00000000
... snip ...

解决Solaris应用程序开发内存泄漏问题 (1)相关推荐

  1. 解决Solaris应用程序开发内存泄漏问题

    作者: 李凌云,张一峰(laoeyu) 内存泄漏是应用软件开发过程中经常会遇到的问题,应用长期内存泄漏会占用大量操作系统内存资源,直接导致应用程序运行不稳定,严重时甚至还会影响到操作系统的正常运行.为 ...

  2. VS C/C++控制台程序添加内存泄漏自动检测功能

    基于MFC框架的应用程序由模板生成时,已经自动添加了内存泄漏自动检测功能,当你的程序有内存泄漏,在Debug调式模式下运行就会在vc的输出窗口里显示出来,容易发现并及时解决.但是我们在写一些测试程序时 ...

  3. Golang程序调试 -- 内存泄漏pprof工具

    Golang程序调试 -- 内存泄漏pprof工具 代码引入pprof WEB访问模式 命令行模式定位内存 命令行模式定位耗时 命令行模式定位内存分配 代码引入pprof import (" ...

  4. 通过NSProxy来解决NSTimer使用不当造成内存泄漏的问题

    NSTimer的一般使用: 1 @interface ViewController : UIViewController 2 @property (nonatomic, strong) NSTimer ...

  5. nstimer循环引用_解决NSTimer循环引用导致内存泄漏的六种方法

    demo放在了GitHub 内存泄漏的原因: self强引用timer.timer添加在runloop上,只要timer不销毁self就销毁不了.当然了你可以选择在viewWillDisappear中 ...

  6. 解决微信小程序开发中wxss中不能用本地图片

    微信小程序开发中wxss中不能用本地图片,我们可以用将我们的图片传到服务器上,然后直接引用在线地址.但是当我们没有服务器时,我们可以用"图床",这个具体可以百度.这里我们用第二种方 ...

  7. 使用 Java 解决现代应用程序开发挑战

    Java 是最流行和广泛使用的编程语言之一,它在开发人员中的受欢迎程度是压倒性的,因为它被千万开发人员使用,并在全球超过百亿个终端上执行.Java 在创建应用程序方面更加强大,这并不是许多行业选择 J ...

  8. 解决微信小程序开发vendor.js文件超过500kb问题

    解决办法: 目前查找到有两种解决办法 1.开发环境启用代码压缩.2.vendor.js的分包处理,目前第一种已经达到预期,仅记录下第一种方式. 1.开发环境启用代码压缩 在build/webpack. ...

  9. 解决微信小程序开发工具右上角没有上传按钮

    原因: AppId没有填对 登录网站 微信公众平台 选择注册的小程序邮箱登录,不是选择测试号,如果进入测试号 ,小程序界面没什么东西,如下界面是测试号节目: 不是上面测试号界面,得是下面这个小程序管理 ...

最新文章

  1. 错误代码大全【100(临时响应)】【200(成功)】【300(已重定向)】【400(请求错误)】【500(服务器错误)】(HTTP协议版本)
  2. SpringSecurity remeber功能源码跟踪
  3. 【BZOJ2407/4398】探险/福慧双修 最短路建模
  4. 瑞友天翼服务器ip地址怎么修改,瑞友天翼发布ERP虚拟程序
  5. linux下隐藏tomcat版本号
  6. 基于深度学习的关系抽取
  7. 百度地图--南京行政区域划分
  8. java简历校园经历_简历校园经历如何编
  9. js获取粘贴的html,JS读取粘贴板内容
  10. Centos7 NAT网络连接方式以及NetworkManger进行网络连接管理
  11. 部分国内外遥感与GIS杂志(小百合)
  12. 【web前端面试题整理07】我不理解表现与数据分离。。。
  13. 2021-09-29 关于间断点相关题目的总结
  14. 289.南信大知网登录
  15. WIN10阻止OA附件打开
  16. Excel-VBA 快速上手(二、条件判断和循环)
  17. 基于B/S的学生网上考试系统(ssh,mysql)
  18. 美业SaaS的创业分享之[定位]:美业SaaS的定位到底是工具还是平台
  19. mx:Label (标签)
  20. ENet: A Deep Neural Network Architecture for Real-Time Semantic Segmentation

热门文章

  1. java基础学习(三)
  2. C++ decltype类型说明符
  3. 黑马就业班(01.JavaSE Java语言基础-11.Java基础加强)——基础加强:Junit单元测试、反射、注解
  4. 多媒体——图片——对图片进行简单加工
  5. 万年历c语言打印年月,万年历(实现从键盘输入1900年之后的任意的某年、某月,输出该月的日历)...
  6. 高鹏清华计算机系,丁高鹏:强身健体为祖国健康工作五十年-清华大学新闻网...
  7. Vue-过滤器的使用
  8. 局部变量能否和成员变量重名?
  9. 哈理工计算机组成原理,哈尔滨理工大学计算机组成原理课程设计.pdf
  10. 面向行业智能,华为数据通信推动的2020之变