使用 gperftools 检测内存泄露

2023-12-06 04:50

本文主要是介绍使用 gperftools 检测内存泄露,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

每个 C/C++ 程序员可能都经历过定位内存泄露问题的痛苦。一方面,为了减少内存泄露问题,在 C++ 程序中,我们应该尽量使用智能指针,在 C 程序中,我们也可以通过一些内存池技术来管理内存的申请与释放。另一方面,当内存泄露问题真的出现时,通过 gperftools 的 heap checker,我们也可以比较轻松地找到内存泄露的线索,缩短问题定位时间。

一、gperftools 简介

gperftools 是 google 开源的一组套件,提供了高性能的、支持多线程的 malloc 实现,以及一组优秀的性能分析工具。

二、安装 gperftools

2.1、下载源码

从 gperftools github 官网上下载最新版本的源码包:

wget https://github.com/gperftools/gperftools/releases/download/gperftools-2.9.1/gperftools-2.9.1.tar.gz

2.2、解压源码包

tar -zxv -f gperftools-2.9.1.tar.gz

2.3、configure

cd gperftools-2.9.1
./configure

命令结束执行后出现一个报错:

configure: WARNING: No frame pointers and no libunwind. Using experimental backtrace capturing via libgcc. Expect crashy cpu profiler.

这是因为没有安装 libunwind。这里直接使用 yum 的方式安装:

yum install libunwind-devel

再次执行 ./configure,命令执行成功。

2.4、编译并安装

执行如下两个命令,进行编译并安装:

make
sudo make install

最后执行 ldconfig 更新动态库文件

2.5、确认安装成功

执行如下命令,确认 gperftools 安装成功

[root@36eab106d3bf gperftools-2.9.1]# pprof --version
pprof (part of gperftools 2.0)Copyright 1998-2007 Google Inc.This is BSD licensed software; see the source for copying conditions
and license information.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.

三、内存泄露检测

gperftools 的 heap checker 组件可以用于检测 C++ 程序中的内存泄露问题。要使用 heap checker,总共分 3 步:

  • 链接 tcmalloc 库到应用程序中
  • 运行程序
  • 分析输出
3.1、链接 tcmalloc 库

heapchecker 是 tcmalloc 的一部分,所以为了在可执行程序中使用 heap checker,在应用程序的链接阶段使用 -ltcmalloc 链接 tcmalloc 库。

3.2、运行代码

使用 heap checker 的推荐方式是 完整程序运行模式,此时 heap checker 可以在程序的 main 函数开始前跟踪内存分配,然后在程序退出时再次检查。如果它发现来了任何内存泄露,heap checker 会直接终止程序(通过 exit(1)),并打印一条消息,告诉你接下来如何使用 pprof 来继续跟踪该内存泄露问题。

完整程序运行heap checker 支持 4 种模式:

  • minimal:在程序初始化期间(即 main 函数运行前)不进行内存泄露检查
  • normal:正常模式,通过跟踪某块内存是否可以被 live object 来访问,来判断是否出现内存泄露
  • strict:类似于 normal 模式,但是对全局对象的内存泄露有一些额外的检查
  • draconian:在该模式下,只有所有申请的内存都被释放,才认为没有出现内存泄露

一般使用 normal 模式就可以满足日常要求了。

heap checker 的另一种使用方法是检测指定代码块是否出现了内存泄露。为了实现这一点,需要在代码片段的开始部分创建一个 HeapLeakChecker 结构体,并在结束部分调用 NoLeaks()。例如:

HeapLeakChecker heap_checker("test_foo");
{code that exercises some foo functionality;this code should not leak memory;
}
if (!heap_checker.NoLeaks()) assert(NULL == "heap memory leak");

需要注意,添加 HeapLeakChecker 只是在程序中添加了内存泄露检测代码,为了真实地检测程序是否出现了内存泄露,仍然需要运行程序,并打开 heap-checker。

env HEAPCHECK=local your_program

除了指定为 local 模式外,之前的 normal 等模式也是可以的,此时除了运行 local 检查外,还将进行 完整程序运行 检查。

当然,运行 heap checker 是有代价的。heap checker 需要记录每次内存申请时的调用栈信息,这就导致了使用 heap checker 时,程序需要消耗更多的内存,同时程序运行速度也更慢。另外,需要注意,由于 heap checker 内部使用了 heap profile 框架,所以不能同时运行 heap checkerheap profile

3.3、忽略已知的内存泄露

对于已知的内存泄露,如果想让 heap checker 忽略这些内存泄露信息,可以在应用程序代码中添加中如下代码:

{HeapLeakChecker::Disabler disabler;<leaky code>
}

另一种方式是使用 IgnoreObject,它接收一个指针参数,对该参数所指向的对象将不再进行内存泄露检查。

3.4、使用 pprof 查看内存泄露结果

heap checker 运行结束时会打印基本的泄露信息,包括调用栈和泄露对象的地址。除此之外,还可以使用 pprof 命令行工具来可视化地查看调用栈。

四、示例

接下来通过一个示例讲述如何使用 gperftools 的 heap checker 来发现程序的内存泄露问题。

4.1、示例程序如下:
// Copyright (C) fuchencong.com#include <iostream>int func() {int *p = new int(10);return 0;
}int main() {std::cout << "memory leak test" << std::endl;return func();
}
4.2、编译程序,并链接 tcmalloc 库:

g++ -std=c++0x -g -o memory_leak memory_leak.cpp -ltcmalloc

4.3、运行程序
[root@36eab106d3bf gperftools-test]# env HEAPCHECK=normal ./memory_leak
WARNING: Perftools heap leak checker is active -- Performance may suffer
memory leak test
Have memory regions w/o callers: might report false leaks
Leak check _main_ detected leaks of 4 bytes in 1 objects
The 1 largest leaks:
*** WARNING: Cannot convert addresses to symbols in output below.
*** Reason: Cannot find 'pprof' (is PPROF_PATH set correctly?)
*** If you cannot fix this, try running pprof directly.
Leak of 4 bytes in 1 objects allocated from:@ 4008ff@ 400935@ 7fc9f81a6555@ 400829If the preceding stack traces are not enough to find the leaks, try running THIS shell command:pprof ./memory_leak "/tmp/memory_leak.16582._main_-end.heap" --inuse_objects --lines --heapcheck  --edgefraction=1e-10 --nodefraction=1e-10 --gvIf you are still puzzled about why the leaks are there, try rerunning this program with HEAP_CHECK_TEST_POINTER_ALIGNMENT=1 and/or with HEAP_CHECK_MAX_POINTER_OFFSET=-1
If the leak report occurs in a small fraction of runs, try running with TCMALLOC_MAX_FREE_QUEUE_SIZE of few hundred MB or with TCMALLOC_RECLAIM_MEMORY=false, it might help find leaks m
Exiting with error code (instead of crashing) because of whole-program memory leaks

这里虽然检测出了内存泄露,但是并没有打印出调用栈的符号信息,根据提示,可能是 PPROF_PATH 环境变量没有正确设置。按照如下方式设置环境变量:

# echo $PPROF_PATH
# which pprof
/usr/local/bin/pprof
# export PPROF_PATH=/usr/local/bin/pprof

再次运行,可以看到已经打印出了内存泄露的栈信息:

[root@36eab106d3bf gperftools-test]# env HEAPPROFILE=./heap_perf HEAPCHECK=normal ./memory_leak
WARNING: Perftools heap leak checker is active -- Performance may suffer
memory leak test
Have memory regions w/o callers: might report false leaks
Leak check _main_ detected leaks of 4 bytes in 1 objects
The 1 largest leaks:
Using local file ./memory_leak.
Leak of 4 bytes in 1 objects allocated from:@ 4008ff func@ 400935 main@ 7f39d94db555 __libc_start_main@ 400829 _startIf the preceding stack traces are not enough to find the leaks, try running THIS shell command:pprof ./memory_leak "/tmp/memory_leak.16586._main_-end.heap" --inuse_objects --lines --heapcheck  --edgefraction=1e-10 --nodefraction=1e-10 --gvIf you are still puzzled about why the leaks are there, try rerunning this program with HEAP_CHECK_TEST_POINTER_ALIGNMENT=1 and/or with HEAP_CHECK_MAX_POINTER_OFFSET=-1
If the leak report occurs in a small fraction of runs, try running with TCMALLOC_MAX_FREE_QUEUE_SIZE of few hundred MB or with TCMALLOC_RECLAIM_MEMORY=false, it might help find leaks m
Exiting with error code (instead of crashing) because of whole-program memory leaks

但是这个信息展现方式并没有直接指出问题产生的行数。我们可以使用其提示的指令,调用可视化工具

pprof ./memory_leak ./heap_perf.0001.heap --inuse_objects --lines --heapcheck  --edgefraction=1e-10 --nodefraction=1e-10 --gv
4.4、错误解决办法
4.4.1、gv错误
gv: Unable to open the display.

说明无法打开显示器,也就是说,–gv选项,需要在带图形界面的系统上使用,我们可以用–svg选项,将生成svg,然后在浏览器打开,命令:

pprof ./memory_leak ./heap_perf.0001.heap --inuse_objects --lines --heapcheck  --edgefraction=1e-10 --nodefraction=1e-10 --svg > svg.svg
4.4.2、addr2line错误

/usr/bin/addr2line: DWARF error: could not find variable specification at offset 5fc4
/usr/bin/addr2line: DWARF error: could not find variable specification at offset 60c0
/usr/bin/addr2line: DWARF error: could not find variable specification at offset 6137

可以在编译的时候将-g换成-pg,例如

g++ -std=c++0x -pg -o memory_leak memory_leak.cpp -ltcmalloc

4.4.3、dot错误

sh: dot: command not found

安装graphviz

yum install graphviz

在这里插入图片描述

五、线上内存泄露检测

5.1、demo

文件夹中的四个文件

BUILD
WORKSPACE
memory_leak.cpp
start.sh
.bazelrc

BUILD文件

cc_binary(name = "memory_leak",linkopts = ["-ltcmalloc",],srcs = ["memory_leak.cpp"],
)

-ltcmalloc必须链接上

memory_leak.cpp文件

#include <iostream>int func() {int *p = new int(10);return 0;
}int main() {std::cout << "memory leak test" << std::endl;return func();
}

start.sh文件

export PPROF_PATH=/usr/local/bin/pprof
nohup env HEAPPROFILE=./heap_perf HEAPCHECK=normal ./memory_leak 2>&1 | tee  "./log.log">> "./log.log" 2>&1 & 

.bazelrc文件

build --copt=-O2
build --copt=-g
build --cxxopt=-std=c++17

WORKSPACE文件为空文件

编译命令

bazel build :memory_leak

如果在生成svg过程中,出现4.4.2错误,将.bazelrc文件中的-g换成-pg

六、参考

https://fuchencong.com/2021/04/22/develop-tools-1/

https://cloud.tencent.com/developer/article/1383795

这篇关于使用 gperftools 检测内存泄露的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/460501

相关文章

Linux系统性能检测命令详解

《Linux系统性能检测命令详解》本文介绍了Linux系统常用的监控命令(如top、vmstat、iostat、htop等)及其参数功能,涵盖进程状态、内存使用、磁盘I/O、系统负载等多维度资源监控,... 目录toppsuptimevmstatIOStatiotopslabtophtopdstatnmon

Android kotlin中 Channel 和 Flow 的区别和选择使用场景分析

《Androidkotlin中Channel和Flow的区别和选择使用场景分析》Kotlin协程中,Flow是冷数据流,按需触发,适合响应式数据处理;Channel是热数据流,持续发送,支持... 目录一、基本概念界定FlowChannel二、核心特性对比数据生产触发条件生产与消费的关系背压处理机制生命周期

java使用protobuf-maven-plugin的插件编译proto文件详解

《java使用protobuf-maven-plugin的插件编译proto文件详解》:本文主要介绍java使用protobuf-maven-plugin的插件编译proto文件,具有很好的参考价... 目录protobuf文件作为数据传输和存储的协议主要介绍在Java使用maven编译proto文件的插件

SpringBoot线程池配置使用示例详解

《SpringBoot线程池配置使用示例详解》SpringBoot集成@Async注解,支持线程池参数配置(核心数、队列容量、拒绝策略等)及生命周期管理,结合监控与任务装饰器,提升异步处理效率与系统... 目录一、核心特性二、添加依赖三、参数详解四、配置线程池五、应用实践代码说明拒绝策略(Rejected

C++ Log4cpp跨平台日志库的使用小结

《C++Log4cpp跨平台日志库的使用小结》Log4cpp是c++类库,本文详细介绍了C++日志库log4cpp的使用方法,及设置日志输出格式和优先级,具有一定的参考价值,感兴趣的可以了解一下... 目录一、介绍1. log4cpp的日志方式2.设置日志输出的格式3. 设置日志的输出优先级二、Window

Ubuntu如何分配​​未使用的空间

《Ubuntu如何分配​​未使用的空间》Ubuntu磁盘空间不足,实际未分配空间8.2G因LVM卷组名称格式差异(双破折号误写)导致无法扩展,确认正确卷组名后,使用lvextend和resize2fs... 目录1:原因2:操作3:报错5:解决问题:确认卷组名称​6:再次操作7:验证扩展是否成功8:问题已解

Qt使用QSqlDatabase连接MySQL实现增删改查功能

《Qt使用QSqlDatabase连接MySQL实现增删改查功能》这篇文章主要为大家详细介绍了Qt如何使用QSqlDatabase连接MySQL实现增删改查功能,文中的示例代码讲解详细,感兴趣的小伙伴... 目录一、创建数据表二、连接mysql数据库三、封装成一个完整的轻量级 ORM 风格类3.1 表结构

使用Docker构建Python Flask程序的详细教程

《使用Docker构建PythonFlask程序的详细教程》在当今的软件开发领域,容器化技术正变得越来越流行,而Docker无疑是其中的佼佼者,本文我们就来聊聊如何使用Docker构建一个简单的Py... 目录引言一、准备工作二、创建 Flask 应用程序三、创建 dockerfile四、构建 Docker

Python使用vllm处理多模态数据的预处理技巧

《Python使用vllm处理多模态数据的预处理技巧》本文深入探讨了在Python环境下使用vLLM处理多模态数据的预处理技巧,我们将从基础概念出发,详细讲解文本、图像、音频等多模态数据的预处理方法,... 目录1. 背景介绍1.1 目的和范围1.2 预期读者1.3 文档结构概述1.4 术语表1.4.1 核

Python使用pip工具实现包自动更新的多种方法

《Python使用pip工具实现包自动更新的多种方法》本文深入探讨了使用Python的pip工具实现包自动更新的各种方法和技术,我们将从基础概念开始,逐步介绍手动更新方法、自动化脚本编写、结合CI/C... 目录1. 背景介绍1.1 目的和范围1.2 预期读者1.3 文档结构概述1.4 术语表1.4.1 核