top命令介绍、实存(RES) 与 虚存(VIRT)区别 ——VIRT持续增长,记一次内存泄漏定位

问题背景与现象:

项目代码基本功能开发完成,各种功能性验证没问题,准备看下长时间运行稳定性如何,所以将程序跑了一晚上,并通过命令 top | grep 程序名将程序运行占用资源显示在终端。

通过一晚上的数据采集发现主要有如下两个问题:

1、%MEM 字段 ,进程使用的物理内存百分比 在缓慢增加( 0.3 ~ 1.4 增加到 1.8 ~ 2.9 )

2、VIRT 字段,进程使用的虚拟内存总量 ,在不断增加 最后显示 15.5+G

分析解决步骤:

A、第一次分析与处理

现象:%MEM 字段 ,进程使用的物理内存百分比 与 VIRT 字段,进程使用的虚拟内存总量,均随运行时间不断增加

分析: 明显的发生了内存泄漏,很容易想到new的数据是否delete。通过分析 问题出在 其他人的 数据模拟模块相关代码中:new 的数据没有delete掉。

优化: 于是优化相关代码后再次验证,确保所有new的数据均得到正确的delete,也包括malloc申请的空间也得到正确的free

B、第二次分析与处理

现象: 再次通过top命令采集数据, %MEM 字段 ,进程使用的物理内存百分比 与 RES 字段进程使用的、未被换出的物理内存大小 不再增加 , 但是VIRT 字段,进程使用的虚拟内存总量还是在不断增加

分析: %MEM 字段 与 RES 字段 不再增加说明第一次分析与处理的效果是有的。继续计算出VIRT字段数据每次增加的量,发现每次增加的是一个固定值。

比较下 实存(RES) 与 虚存(VIRT) 具体包括什么:

VIRT: 进程“需要的”虚拟内存大小,包括进程使用的库、代码、数据,以及malloc、new分配的堆空间和分配的栈空间等;

RES: 进程当前使用的内存大小,包括使用中的malloc、new分配的堆空间和分配的栈空间,但不包括swap out量;

相比于 RESVIRT还包括了进 程使用的库、代码、数据,受到 文章 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不断增加的原因,进一步分析代码问题还是出在数据模拟模块相关代码中。重新优化代码后再次运行,问题得到了解决

top命令返回数据介绍

在这里插入图片描述

前五行信息

统计信息区前五行是系统整体的统计信息
在这里插入图片描述

第一行 任务队列信息

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 — 进程名称(命令名/命令行)

实存(RES) 与 虚存(VIRT)分析

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 <iostream>
#include <cstdio>
#include <cstring>

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:

在这里插入图片描述

使用500M:
在这里插入图片描述

delete 后
在这里插入图片描述

线程未释放导致内存泄漏分析

代码示例

#include <iostream>
#include <cstring>
#include <chrono>
#include <thread>

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

  • 13
    点赞
  • 59
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

墨1024

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值