项目代码基本功能开发完成,各种功能性验证没问题,准备看下长时间运行稳定性如何,所以将程序跑了一晚上,并通过命令 top | grep 程序名
将程序运行占用资源显示在终端。
通过一晚上的数据采集发现主要有如下两个问题:
1、%MEM
字段 ,进程使用的物理内存百分比 在缓慢增加( 0.3 ~ 1.4 增加到 1.8 ~ 2.9 )
2、VIRT
字段,进程使用的虚拟内存总量 ,在不断增加 最后显示 15.5+G
现象:%MEM
字段 ,进程使用的物理内存百分比 与 VIRT
字段,进程使用的虚拟内存总量,均随运行时间不断增加
分析: 明显的发生了内存泄漏,很容易想到new
的数据是否delete
。通过分析 问题出在 其他人的 数据模拟模块相关代码中:new
的数据没有delete
掉。
优化: 于是优化相关代码后再次验证,确保所有new
的数据均得到正确的delete
,也包括malloc
申请的空间也得到正确的free
。
现象: 再次通过top
命令采集数据, %MEM
字段 ,进程使用的物理内存百分比 与 RES
字段进程使用的、未被换出的物理内存大小 不再增加 , 但是VIRT
字段,进程使用的虚拟内存总量还是在不断增加
分析: %MEM
字段 与 RES
字段 不再增加说明第一次分析与处理的效果是有的。继续计算出VIRT
字段数据每次增加的量,发现每次增加的是一个固定值。
比较下 实存(RES
) 与 虚存(VIRT
) 具体包括什么:
VIRT: 进程“需要的”虚拟内存大小,包括进程使用的库、代码、数据,以及malloc、new分配的堆空间和分配的栈空间等;
RES: 进程当前使用的内存大小,包括使用中的malloc、new分配的堆空间和分配的栈空间,但不包括swap out量;
相比于 RES
,VIRT
还包括了进 程使用的库、代码、数据,受到 文章 http://bbs.chinaunix.net/thread-682816-1-1.html 的启发对 线程的申请释放进行了排查,通过 函数 pthread_setname_np 给每个线程取不同名字(15个字符) 参见 https://blog.csdn.net/lqy971966/article/details/104752947 ,重新编译并运行程序,通过 命令ps -T -p 进程号
查看某个进程的线程 ,可以发现有几个名字的线程数量在不断增加,线程没有回收释放,而这正是导致VIRT
不断增加的原因,进一步分析代码问题还是出在数据模拟模块相关代码中。重新优化代码后再次运行,问题得到了解决
第一行 任务队列信息
13:54:09 当前时间
up 1:16 系统运行时间,格式为时:分
1 user 当前登录用户数
load average: 0.24, 0.05, 0.02 系统负载,即任务队列的平均长度。三个数值分别为 1分钟c、5分钟、15分钟前到现在的平均值。
第二、三行 任务和CPU的信息
任务:
total 进程总数
running 正在运行的进程数
sleeping 睡眠的进程数
stopped 停止的进程数
zombie 僵尸进程数
%Cpu(s):
0.5 us 用户空间占用CPU百分比
1.4 sy 内核空间占用CPU百分比
0.0 ni 用户进程空间内改变过优先级的进程占用CPU百分比
98.1 id 空闲CPU百分比
0.0 wa 等待输入输出的CPU时间百分比
0.0 hi 硬件CPU中断占用百分比
0.0 si 软中断占用百分比
0.0 st 虚拟机占用百分比
第四、五行 内存信息
KiB Mem:
4002264 total 物理内存总量
1213228 free 空闲内存总量
1703096 used 使用的物理内存总量
1085940 buff/cache 用作内核缓存的内存量
KiB Swap:
1942896 total 交换区总量
1942896 free 空闲交换区总量
0 used 使用的交换区总量
1992616 avail Mem 可用交换区总量
依次对应:
PID — 进程id
USER — 进程所有者
PR — 进程优先级
NI — nice值。负值表示高优先级,正值表示低优先级
VIRT — 进程使用的虚拟内存总量,单位kb。VIRT=SWAP+RES
RES — 进程使用的、未被换出的物理内存大小,单位kb。RES=CODE+DATA
SHR — 共享内存大小,单位kb
S — 进程状态。D=不可中断的睡眠状态 R=运行 S=睡眠 T=跟踪/停止 Z=僵尸进程
%CPU — 上次更新到现在的CPU时间占用百分比
%MEM — 进程使用的物理内存百分比
TIME+ — 进程使用的CPU时间总计,单位1/100秒
COMMAND — 进程名称(命令名/命令行)
VIRT:
1、进程“需要的”虚拟内存大小,包括进程使用的库、代码、数据,以及malloc、new分配的堆空间和分配的栈空间等;2、假如进程新申请10MB的内存,但实际只使用了1MB,那么它会增长10MB,而不是实际的1MB使用量。
RES:
1、进程当前使用的内存大小,包括使用中的malloc、new分配的堆空间和分配的栈空间,但不包括swap out量;
2、包含其他进程的共享;
3、如果申请10MB的内存,实际使用1MB,它只增长1MB;
SHR:
1、除了自身进程的共享内存,也包括其他进程的共享内存;
2、虽然进程只使用了几个共享库的函数,但它包含了整个共享库的大小;
3、计算某个进程所占的物理内存大小公式:RES – SHR;
4、swap out后,它将会降下来。
#include
#include
#include
int main()
{
int test = 0;
//分配1024M, 未使用
char * p = new char [1024*1024*1024];
scanf("%d", &test); //等待输入 便于top查看
//使用50M
memset(p, 0, 1024 * 1024 * 50);
scanf("%d", &test); //等待输入 便于top查看
//使用500M
memset(p, 0, 1024 * 1024 * 500);
scanf("%d", &test); //等待输入 便于top查看
delete [] p;
scanf("%d", &test); //等待输入 便于top查看
return 0;
}
可以通过 top 命令 按着 VIRT 排序 获取到 进程ID,通过 top -p 进程ID
命令 便于查看
分配1024M, 未使用:
使用50M:
#include
#include
#include
#include
void testThread() {
std::cout << "testThread in" << std::endl;
char *p = new char [1024*1024*50];
//使用50M
memset(p, 0, 1024 * 1024 * 50);
std::this_thread::sleep_for(std::chrono::seconds(1));
delete [] p;
p = nullptr;
getchar(); // 模拟线程出问题 不退出情况
std::cout << "testThread out" << std::endl;
}
int main() {
std::cout << "Hello, World!" << std::endl;
int count = 10000;
while(count--) {
std::thread test(testThread);
pthread_setname_np(test.native_handle(),"testThread");
test.detach();
std::this_thread::sleep_for(std::chrono::seconds(10));
}
return 0;
}
由于 线程没有退出 , 通过 命令 ps -T -p 进程ID
可以发现子线程不断增加,由于子线程中有正确的内存分配释放,可以看到图中 %MEM
字段 与 RES
字段 数值没有不断增加,由于线程没有释放 可以看到图中VIRT
字段数值在不断增加
数据分析,我们创建了8个线程并且没有销毁,通过命令也可以发现 进程有8个子线程,但是数据有11条,这是通过命令top | grep Test03
获取数据时额外打印new了数据还未delete的状态, 在线程中分配并使用了50M数据,我们剔除RES
字段52000+的数据,剩下刚好8条,每次增加的应该就是线程未释放占用的内存大小,这里固定为 73732 KB 约等于 72M,包括使用的库、代码、数据,以及malloc、new分配的堆空间和分配的栈空间等。
屏蔽掉代码中如下代码,重新运行状态正常
getchar(); // 模拟线程出问题 不退出情况
http://bbs.chinaunix.net/thread-682816-1-1.html
https://blog.csdn.net/lqy971966/article/details/104752947
https://www.cnblogs.com/sea520/p/12680709.html
https://www.cnblogs.com/ggjucheng/archive/2012/01/08/2316399.html
https://www.cnblogs.com/xudong-bupt/p/8643094.html