【Linux】使用Valgrind定位内存增长问题

文章目录

      • 1 内存问题
      • 2 Valgrind
        • 2.1 Valgrind介绍
        • 2.2 Valgrind中的Memcheck
        • 2.3 Valgrind中的Massif
      • 3 总结

1 内存问题

内存问题是一类比较难以定位的问题,通常有两类场景:

  • 程序在低负载情况下的内存使用量是否正常,低负载情况下不应该太高
  • 程序在高负载情况下的内存使用量是否正常,高负载情况下应该为内存使用量设置阈值,防止触发OOM

因此,为了检查内存使用量是否正常,需要知道当前的内存使用量以及哪里分配的内存造成内存增长

使用topps命令可以查看某个进程的内存使用量,然后结合代码中初始分配的内存大小评估当前的内存使用量是否正常。

2 Valgrind

2.1 Valgrind介绍

Valgrind是Linux上的一套开源的动态分析工具集,通常用来检测和分析程序中的错误,提高程序的稳定性和性能。

Valgrind整体架构上包含内核和周边工具集,将程序放到内核模拟的仿真环境中运行,并提供一些能力接口,然后基于这些接口实现周边工具。

2.2 Valgrind中的Memcheck

Memcheck是最常用且是默认的工具,通常用于检查内存泄漏等问题。

示例1:

#include <iostream>int main() {int *ptr = nullptr;*ptr = 0;return 0;
}

编译并执行:

g++ -o mem3 -g mem3.cpp
valgrind --leak-check=full ./mem3

输出:

==43290== Memcheck, a memory error detector
==43290== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==43290== Using Valgrind-3.17.0 and LibVEX; rerun with -h for copyright info
==43290== Command: ./mem3
==43290== 
==43290== Invalid write of size 4
==43290==    at 0x10917D: main (mem3.cpp:5)
==43290==  Address 0x0 is not stack'd, malloc'd or (recently) free'd
==43290== 
==43290== 
==43290== Process terminating with default action of signal 11 (SIGSEGV)
==43290==  Access not within mapped region at address 0x0
==43290==    at 0x10917D: main (mem3.cpp:5)
==43290==  If you believe this happened as a result of a stack
==43290==  overflow in your program's main thread (unlikely but
==43290==  possible), you can try to increase the size of the
==43290==  main thread stack using the --main-stacksize= flag.
==43290==  The main thread stack size used in this run was 8388608.
==43290== 
==43290== HEAP SUMMARY:
==43290==     in use at exit: 0 bytes in 0 blocks
==43290==   total heap usage: 1 allocs, 1 frees, 72,704 bytes allocated
==43290== 
==43290== All heap blocks were freed -- no leaks are possible
==43290== 
==43290== For lists of detected and suppressed errors, rerun with: -s
==43290== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
Segmentation fault

从上面的输出可以看出:

  • mem3.cpp的第5行进行了地址的非法写操作
  • 程序收到SIGSEGV信号而终止,是由于访问了0x0地址
  • 所有的堆内存都被释放,没有内存泄漏

示例2:

#include <iostream>int main() {int *ptr = (int*)malloc(sizeof(int));*ptr = 0;return 0;
}

输出:

==45775== Memcheck, a memory error detector
==45775== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==45775== Using Valgrind-3.17.0 and LibVEX; rerun with -h for copyright info
==45775== Command: ./mem4
==45775== 
==45775== 
==45775== HEAP SUMMARY:
==45775==     in use at exit: 4 bytes in 1 blocks
==45775==   total heap usage: 2 allocs, 1 frees, 72,708 bytes allocated
==45775== 
==45775== 4 bytes in 1 blocks are definitely lost in loss record 1 of 1
==45775==    at 0x4843839: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==45775==    by 0x10919E: main (mem4.cpp:4)
==45775== 
==45775== LEAK SUMMARY:
==45775==    definitely lost: 4 bytes in 1 blocks
==45775==    indirectly lost: 0 bytes in 0 blocks
==45775==      possibly lost: 0 bytes in 0 blocks
==45775==    still reachable: 0 bytes in 0 blocks
==45775==         suppressed: 0 bytes in 0 blocks
==45775== 
==45775== For lists of detected and suppressed errors, rerun with: -s
==45775== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

从上面的输出可以看出:

  • in use at exit表明有4个字节在程序退出时还在使用,下面的4 bytes in 1 blocks are definitely lost也表明有内存没有释放
  • LEAK SUMMARY是对内存泄漏的汇总信息

Memcheck可以检测程序中的常见的内存分配但不释放、访问越界等问题,但是,有时候有指针指向内存块,只是程序还没有释放且继续申请内存,运行一段时间后由于OOM退出,这时候Memcheck就无能为力了,而且现代的程序比较复杂,Memcheck会有很多误告。

2.3 Valgrind中的Massif

内存持续增长一般有两种场景:

  • 出现死循环或者递归不终止,造成栈溢出
  • 持续分配堆内存,而不释放,此时可能存在内存泄漏或者是程序逻辑处理异常而没有正确释放内存(当然,也有可能是死循环引起的)

对于第一个问题,可以通过日志和进程当前的快照确认,而且,出现这种情况时通常会造成线程的CPU高。

对于第二个问题,就是要知道程序在哪里出现了持续分配堆内存,然后可以审查这部分代码,看是否存在没有释放的场景。那怎么知道程序在哪里分配堆内存呢?就要用到Valgrind中的Massif工具。

Massif工具通过采样的方式获取当前内存分配的情况,并且可以输出分配内存的调用栈。

示例代码:

#include <iostream>
#include <unistd.h>void func() {while(true) {char *ptr = (char *)malloc(1024*sizeof(char));sleep(1);}
}int main() {func();return 0;
}

使用Valgrind执行该程序:valgrind --tool=massif --time-unit=B ./mem5,然后将输出文件转换成文本:ms_print massif.out.47145 > memory.log

结果文件分为三个部分。

第一部分是一个简单的说明,表明生成该采样文件执行的命令、Massif和ms_print的参数。

由于Massif工具的工作方式是对堆内存的使用情况进行快照,因此,第二部分的坐标图和第三部分的表格记录的都是快照的情况,有的快照包含内存的使用量和分配堆内存的调用栈,有的快照只包含内存的使用量,因此将快照分为详细快照普通快照

第二个部分是一个用符号生成的坐标图:

    KB
122.4^                                             ##########################|                                           :@#|                                         :::@#|                                      ::@:::@#|                                    @:::@:::@#|                                  ::@:::@:::@#|                                :@::@:::@:::@#|                             ::::@::@:::@:::@#|                           ::@:::@::@:::@:::@#|                          :::@:::@::@:::@:::@#|                          :::@:::@::@:::@:::@#|                          :::@:::@::@:::@:::@#|                          :::@:::@::@:::@:::@#                         :|                          :::@:::@::@:::@:::@#                         :|                          :::@:::@::@:::@:::@#                         :|                          :::@:::@::@:::@:::@#                         :|                          :::@:::@::@:::@:::@#                         :|                          :::@:::@::@:::@:::@#                         :|                          :::@:::@::@:::@:::@#                         :|                          :::@:::@::@:::@:::@#                         :0 +----------------------------------------------------------------------->KB0                                                                   193.4Number of snapshots: 55Detailed snapshots: [9, 19, 29, 39, 49, 53 (peak)]

坐标图的横轴表示采样的间隔,不同的单位有不同的含义:

  • i:默认的单位,表示每次快照之间的指令执行数量
  • ms:毫秒,表示从程序启动之后的时间
  • B:字节,表示分配的内存量,单位是B

坐标图的纵坐标表示分配的堆内存大小,所以从图里面可以直观的看出堆内存分配的趋势。

坐标图中的符号的含义:

  • 冒号:该字符组成的柱状图表示普通快照,只记录内存的使用量
  • @:该字符组成的柱状图表示详细快照,记录内存的使用量和分配内存的调用栈
  • 井号:该字符组成的柱状图表示峰值快照,记录内存的使用量和分配内存的调用栈

在坐标图的下面还列出了记录快照的数量:

  • 总共记录了55个快照
  • 详细快照有6个,里面也包含峰值快照

因此,看第二个部分就可以看到大概的内存增长趋势:堆内存是一直增长的。

第三部分是记录的所有快照的数据,以前10个快照为例:

--------------------------------------------------------------------------------n        time(B)         total(B)   useful-heap(B) extra-heap(B)    stacks(B)
--------------------------------------------------------------------------------0              0                0                0             0            01         72,712           72,712           72,704             8            02         73,744           73,744           73,728            16            03         74,776           74,776           74,752            24            04         75,808           75,808           75,776            32            05         76,840           76,840           76,800            40            06         77,872           77,872           77,824            48            07         78,904           78,904           78,848            56            08         79,936           79,936           79,872            64            09         80,968           80,968           80,896            72            0
99.91% (80,896B) (heap allocation functions) malloc/new/new[], --alloc-fns, etc.
->89.79% (72,704B) 0x4903B79: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.29)
| ->89.79% (72,704B) 0x4011B1D: call_init.part.0 (dl-init.c:70)
|   ->89.79% (72,704B) 0x4011C07: call_init (dl-init.c:33)
|     ->89.79% (72,704B) 0x4011C07: _dl_init (dl-init.c:117)
|       ->89.79% (72,704B) 0x4001109: ??? (in /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2)
|
->10.12% (8,192B) 0x1091BE: func() (mem5.cpp:6)->10.12% (8,192B) 0x1091DB: main (mem5.cpp:12)

快照的信息组成一个表格:

  • n:快照的序号,从0开始,对应的就是第二部分中详细快照中的序号
  • time:时间,对应的是坐标图中的横轴,这里的time_unit设置的是B,因此,横轴的单位是B,而且由于横坐标是B,横纵坐标值是一样的
  • total:分配的内存总量,对应的是坐标图中的纵轴,单位是B
  • useful-heap:程序申请的内存量
  • extra-heap:超过程序申请的内存量,例如管理内存需要的元数据,一般来说,这部分在整体分配的内存中的占比可以忽略
  • stacks:栈的大小,默认情况下,栈的分析是关闭的,会降低Massif的速度,可以通过--stacks=yes开启

普通快照详细快照都会包含上述信息,而详细快照还包含分配这些内存的调用栈。

例如,序号为9的快照就是详细快照,首先给出了程序申请的内存(useful-heap)占内存总量(total)的占比以及堆分配函数(malloc/new/new[]):

99.91% (80,896B) (heap allocation functions) malloc/new/new[], --alloc-fns, etc.

然后就是分配内存的堆栈,这里包含两个栈,第一个栈是:

->89.79% (72,704B) 0x4903B79: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.29)
| ->89.79% (72,704B) 0x4011B1D: call_init.part.0 (dl-init.c:70)
|   ->89.79% (72,704B) 0x4011C07: call_init (dl-init.c:33)
|     ->89.79% (72,704B) 0x4011C07: _dl_init (dl-init.c:117)
|       ->89.79% (72,704B) 0x4001109: ??? (in /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2)

该堆栈是Valgrind自身分配的内存,一般可以忽略。

第二个栈是:

->10.12% (8,192B) 0x1091BE: func() (mem5.cpp:6)->10.12% (8,192B) 0x1091DB: main (mem5.cpp:12)

从该调用栈就可以知道8192B是从哪里分配的,注意调用栈是下面调用上面,也就是说,mem5.cpp的第6行的func()函数中的代码分配的。

有时候在调用栈中还会看到02.18% (1,891,902B) in 546 places, all below massif's threshold (1.00%)这样的描述,是因为有些小的内存分配对整体影响不大就没有记录调用栈,一般都可以直接忽略,如果想看到所有的调用栈,可以设置--threshold参数。

3 总结

当程序运行过程中,内存持续增长,一段时间后就可能造成OOM,此时可以使用Valgrind的Massif工具分析程序执行过程中的堆分配内存量。

在使用Valgrind的Massif工具运行程序后就可以得到进程在运行过程中的堆内存分配情况以及对应的栈,对分配内存较多的代码进行审查并优化,或者添加一些限制,减少分配的内存。

使用过程中需要注意两个地方:

  • Massif工具只能用来分析堆内存的使用,也就是通过malloc/new/new[]分配的内存,有些可能是通过mmap分配的内存就没办法分析了
  • 对于运行时间较短的程序,需要加上--time-unit=B参数

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/837457.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python基础语法【1】

做个简单的总结 1.输出 直接通过print函数进行输出 print("Hello") 2.变量 2.1命名规则 变量名只能包含字母、数字和下划线。变量名能以字母或下划线打头&#xff0c;但不能以数字打头。例如&#xff0c;可将变量命名为message_1&#xff0c;但不能将其命名为1…

spring-activiti 一些操作

文章目录 一、bpmn文件部署二、发起一个流程三、查找任务四、提交/完成任务五、流程定义的查询六、流程的删除七、流程历史信息查看八、指派任务九、委派任务十、放回任务十一、回退任务十二、终止任务 一、bpmn文件部署 AurowardRepositoryService rep;//通过流区创建一个工作…

什么是 PowerShell

什么是 PowerShell&#xff1f; PowerShell 是一种跨平台的任务自动化解决方案&#xff0c;由命令行 shell、脚本语言和配置管理框架组成。 PowerShell 在 Windows、Linux 和 macOS 上运行。 命令行 Shell ​ PowerShell 是新式命令 shell&#xff0c;其中包括其他常用 shel…

(接上一篇linux rocky 搭建DNS高阶版)实现不同网段访问解析不同的服务器并加域

上一篇链接&#xff1a;linux rocky 搭建DNS服务和禁止AD域控DNS&#xff0c;做到独立DNS并加域-CSDN博客文章浏览阅读417次&#xff0c;点赞13次&#xff0c;收藏7次。使用linux rocky 搭建DNS服务&#xff0c;用于独立AD域控DNS存在&#xff0c;并且实现加域。https://blog.c…

【算法刷题day53】Leetcode:1143. 最长公共子序列、1035. 不相交的线、53. 最大子数组和

文章目录 Leetcode 1143. 最长公共子序列解题思路代码总结 Leetcode 1035. 不相交的线解题思路代码总结 Leetcode 53. 最大子数组和解题思路代码总结 草稿图网站 java的Deque Leetcode 1143. 最长公共子序列 题目&#xff1a;1143. 最长公共子序列 解析&#xff1a;[代码随想录…

数字集成电路物理设计[陈春章]——知识总结与精炼01

第一章 集成电路物理设计方法 1.1 数字集成电路设计挑战 1.2 数字集成电路设计流程 前两节内容讲述的是数字集成电路发展与流程&#xff0c;知识体系比较宏观和简单&#xff0c;请读者自行了解即可。 1.3 数字集成电路设计收敛 实现设计收敛任务&#xff1a;①数据系统;②优…

Flume 的安装和使用方法(Spark-2.1.0)

一、Flume的安装 1.下载压缩包 https://www.apache.org/dyn/closer.lua/flume/1.7.0/apache-flume-1.7.0-bin.tar.gz 2.上传到linux中 3.解压安装包 cd #进入加载压缩包目录sudo tar -zxvf apache-flume-1.7.0-bin.tar.gz -C /usr/local # 将 apache-flume-1.7.0-bin.tar.g…

夯实spring(二十一):@Scope、@DependsOn、@ImportResource、@Lazy

本文问题: 1. Scope是做什么的&#xff1f;常见的用法有几种&#xff1f;2. DependsOn是做什么的&#xff1f;常见的用法有几种&#xff1f;3. ImportResource干什么的&#xff1f;通常用在什么地方&#xff1f;4. Lazy做什么的&#xff0c;通常用在哪些地方&#xff1f;常见的…

leetcode-15. 三数之和

题目描述 给你一个整数数组 nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k &#xff0c;同时还满足 nums[i] nums[j] nums[k] 0 。请 你返回所有和为 0 且不重复的三元组。 注意&#xff1a;答案中不可以包含重复的三元组…

有哪些值得买的开放式耳机推荐?2024年开放式运动耳机选购指南

开放式耳机因其独特设计&#xff0c;能在一定程度上保护听力。相较于传统封闭式耳机&#xff0c;开放式设计允许周围环境声音自然流入耳内&#xff0c;降低了耳内共振和声压&#xff0c;减少了耳道的不适感&#xff0c;从而减轻了对听力的潜在损害。对于追求音质与听力保护并重…

iOS git创建与合并分支

参考文章 Git – 创建与合并分支 相关命令行解释 1. 创建并切换到该分支&#xff1a; 2.0.0是新建的分支的名字 git checkout -b 2.0.0 相当于下面这两句 创建分支 git branch 2.0.0 切换到某个分支 git checkout 2.0.0 2. 查看当前所有分支&#xff1a; git branch 如何提…

国外新闻媒体推广:多元化媒体分发投放-大舍传媒

前言 &#xff1a;随着全球化的进程&#xff0c;国外新闻市场呈现出快速发展的趋势。在这个趋势下&#xff0c;国外新闻媒体推广成为了各行业企业宣传业务的重要一环。本文将重点介绍大舍传媒的多元化媒体分发投放服务&#xff0c;以及对国外新闻媒体推广的意义。 1. 多元化媒…

阿赵UE引擎C++编程学习笔记——解决中文乱码问题

大家好&#xff0c;我是阿赵。   在UE编写C的时候&#xff0c;可能有些朋友发现&#xff0c;在C里面如果打印输出或者赋值一些中文的字符串的时候&#xff0c;会出现各种的报错&#xff0c;要么乱码&#xff0c;要么直接编译不过。   这个问题&#xff0c;其实和UE本身没什…

OSEK应用模式

1 前言 应用模式&#xff08;Application modes)用于区分不同的场景&#xff0c;以便在系统运行时&#xff0c;组织各自相互独立的OS相关的资源集合&#xff0c;是一种分而治之的思想体现。不同的应用模式是互斥的&#xff0c;即系统当前必须在一种应用模式&#xff08;且只能在…

Java面试八股之反射慢在哪里

Java反射慢在哪里 动态类型检查&#xff1a; 在反射过程中&#xff0c;Java需要在运行时确定类、方法、字段等的类型信息。这与编译时已经确定类型信息的常规对象访问不同&#xff0c;反射需要额外的类型查询和验证&#xff0c;增加了性能开销。 安全检查&#xff1a; 反射…

重生之我在地球当程序员-SpringMVC篇

重生之我在地球当程序员-SpringMVC篇 本篇主要讲述了SpringMVC框架的使用&#xff0c;以及SpringMVC相关知识点的介绍 SpringMVC 是应用web层的 基于MVC设计模式的轻量级的web框架。 对Servlet封装&#xff0c;支持restful风格 MVC概念和三层架构 MVC Spring MVC中&#xff…

高亚科技签约山东亿海兰特,打造合同及项目一体化管理平台!

近日&#xff0c;中国企业管理软件资深服务商高亚科技与山东亿海兰特通信科技有限公司&#xff08;以下简称“山东亿海兰特”&#xff09;正式签约&#xff0c;依托8Manage PM业务项目管理软件&#xff0c;打造集客户、合同、项目、交付于一体的运营平台&#xff0c;提升企业全…

对文本框做字数限制

效果图 实现步骤 其中绝对布局根据需求自行调整 <!--单文本输入框--> <div class"form-group"><label class"col-sm-2 control-label is-required">面试公司&#xff1a;</label><div class"col-sm-9"><input …

前端崽的java study笔记

文章目录 basic1、sprint boot概述2、sprint boot入门3、yml 配置信息书写和获取 持续更新ing~ basic 1、sprint boot概述 sprint boot特性&#xff1a; 起步依赖&#xff08;maven坐标&#xff09;&#xff1a;解决配置繁琐的问题&#xff0c;只需要引入sprint boot起步依赖的…