本文转自 http://luckywhu.blog.163.com/blog/static/184077944201272162239405/


程序在长时间压力测试之后发生core,检查core文件的堆栈,发现最后失败的地方是C++里面对new的调用。

部分堆栈如下

(gdb) bt

#0 0x000000302af69447 in _int_malloc () from /lib64/tls/libc.so.6

#1 0x000000302af6acf0 in malloc () from /lib64/tls/libc.so.6

#2 0x000000302d3af59a in operator new(unsigned long) () from /usr/lib64/libstdc++.so.6

#3 0x000000302d3af6a9 in operator new[](unsigned long) () from /usr/lib64/libstdc++.so.6

        由于之前程序中,曾经发生过因为系统内存耗尽而malloc失败的事情。怀疑还是内存泄露的问题。

于是在测试过程中监测内存耗用。发现一个奇怪的现象。top时。实际物理内存(RES)消耗没有上涨,但虚拟内存(VIRT)使用却不断上升直至几百GB。最后依然是malloc失败。

首先分析程序中需要申请大量内存的地方,没有发现问题,之后分析/proc/pid/status里面的内存VmSize的上涨情况,发现上涨每次初始化的阶段上涨525MB,释放空间的时候回收423MB,也就是说每轮循环有82MB左右的内存泄露了。非常有规律。

Pmap 查看进程地址空间分配情况,发现大量10MB大小的段。也就是说每次泄露的应该是10MB左右。

  无奈只能GDB跟踪程序的执行,同时观察进程的VIRT占用情况。设了几个断点之后发现每次有新线程创建时内存就有上涨情况。数了一下创建的线程数目是8个,这是client端在初始化到server的连接时连接池的大小。而每个线程差不多平均10MB的内存。在server端函数中添加日志,发现client端断掉连接之后,server端并没有相应的清理创建的这8个线程。

查看线程创建部分的代码。

int Thread::start()

{

int err = pthread_create(tid_, 0, Thread::threadRunner, parm_);

if(err)

{

return THREAD_CREATE_ERROR;

}

 

if(!detach_)

{

if(pthread_join(*tid_, 0))

{

return THREAD_JOIN_ERROR;

}

   

return parm_->retval_;

}

   

return 0;

} 

创建线程时,由于是pthread_attr_tNULL,也就是说创建的线程是Joinable的。程序中detachtrue,因此所有的线程都没有被join。其执行完毕时,资源也都没有被回收,这正是VIRT内存上涨的原因。

解决方法有两个,一个是在创建线程时即指定其属性为detached的,这样线程执行完毕时会自动回收资源。另一个办法是在创建线程之后执行pthread_detach,将线程属性设置为detached

通过添加

pthread_detach(*tid_);

问题解决,内存不再上涨。

PS

在调查该问题过程中,也发另外一个问题,在Linux下编写程序时,程序运行过程中无论是RES持续上涨还是VIRT上涨,一定是由于某种原因导致的内存泄露引起。我们一般关注较多的是RES。例如下面这段代码。

#include <iostream>

using namespace std;

int main(void)

{

uint32_t* ip;

while(1){

ip = new uint32_t[100000000];

sleep(1);

}

}

该段代码很明显有内存泄露,但TOP 出来的内存RES并没有上涨,只是VIRT上涨。出现这种现象是因为linux在分配内存时采用了一种取巧的策略,即分配时分配的是虚拟地址空间,在实际使用时才映射到物理地址。这样运行中观察到的只是VIRT上涨。此时内存已经发生泄露,虽然VIRT会上涨到实际物理内存的很多倍,但最终还是会导致问题。

Logo

更多推荐