linux线程资源回收的问题(虚拟内存)
本文转自 http://luckywhu.blog.163.com/blog/static/184077944201272162239405/程序在长时间压力测试之后发生core,检查core文件的堆栈,发现最后失败的地方是C++里面对new的调用。部分堆栈如下(gdb) bt #00x000000302af69447 in _int_malloc ()
本文转自 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_t为NULL,也就是说创建的线程是Joinable的。程序中detach为true,因此所有的线程都没有被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会上涨到实际物理内存的很多倍,但最终还是会导致问题。
更多推荐
所有评论(0)