内存优化-使用tcmalloc分析解决内存泄漏和内存暴涨问题

本文涉及的产品
轻量应用服务器 2vCPU 4GiB,适用于搭建Web应用/小程序
轻量应用服务器 2vCPU 4GiB,适用于搭建容器环境
轻量应用服务器 2vCPU 4GiB,适用于网站搭建
简介: 其实我一直很想写关于tcmalloc的内存泄漏检测的文章,只是一直记不起来该如何下笔,有时项目太忙,在整理这方便的思考过少,另外遇到的问题也不是很多,直到最近用tcmalloc帮A项目排查...

其实我一直很想写关于tcmalloc的内存泄漏检测的文章,只是一直记不起来该如何下笔,有时项目太忙,在整理这方便的思考过少,另外遇到的问题也不是很多,直到最近用tcmalloc帮A项目排查一些很棘手的内存泄漏问题,有了一定的创作灵感,这才执笔创作,希望能分享给大家。

如果对tcmalloc还不是很了解的可以看我以前的文章:

内存优化-比glibc更快的tcmalloc

内存优化-如何使用tcmalloc来提升内存性能?提升的结果太不可思议

下载安装tcmalloc

#1、到google下载代码:

当然你最好下载最新或者最稳定版本,这里比如下载2.1版本:

wget https://gperftools.googlecode.com/files/gperftools-2.1.tar.gz

#解压

tar -zxvf google-perftools-2.1.tar.gz

#看看说明

cd google-perftools-2.1

./configure -h

./configure

make && make install

代码中使用tcmalloc替换malloc

我们如何使用tcmalloc来替换glibc的malloc呢?

在链接tcmalloc的时候我们可以使用以下任意一种方式:

1.启动程序之前,预先加载tcmalloc动态库的环境变量设置: exportLD_PRELOAD="/usr/local/lib/libtcmalloc.so"

2.在你的动态库链接的地方加入:-ltcmalloc

检测内存泄漏

測试代码1:

#include <iostream>
using namespace std;
int main()
{
        int *p = new int();
        return 0;
}

image.gif

编译:g++ t.cpp -o main -ltcmalloc -g -O0

内存泄漏检查:   env HEAPCHECK=normal ./main

结果:

root@ubuntu:/home/gaoke/test# env HEAPCHECK=normal ./main
WARNING: Perftools heap leak checker is active -- Performance may suffer
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:
  @ 4007ef 
  @ 7f7895a64f45 
  @ 400719 
If the preceding stack traces are not enough to find the leaks, try running THIS shell command:
pprof ./main "/tmp/main.6712._main_-end.heap" --inuse_objects --lines --heapcheck  --edgefraction=1e-10 --nodefraction=1e-10 --gv
If 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 more repeatably
Exiting with error code (instead of crashing) because of whole-program memory leaks

image.gif

大家注意,这里有关键字Leak,你就得当心这里可能存在内存泄漏,提示

Leak of 4 bytes in 1 objects allocated from

对,是有四字节的内存泄漏,虽然你看代码能看到指针p未释放,但是这里你需要掌握的是在你无法直观的通过阅读代码来找到内存泄漏点的情况下,如何用tcmalloc工具来分析问题。

相信细心的你会注意到运行输出的这一行

pprof ./main "/tmp/main.6712._main_-end.heap" --inuse_objects --lines --heapcheck  --edgefraction=1e-10 --nodefraction=1e-10 --gv

这里就是我要重点讲的pprof工具

google-perftool提供了一个叫pprof的工具,它是一个perl的脚本,通过这个工具,可以将google-perftool的输出结果分析得更为直观,输出为text、图片、pdf等格式。

这里我们把结果通过text的方式输出:你只需要把刚才的--gv换成--text

pprof ./main "/tmp/main.6712._main_-end.heap" --inuse_objects --lines --heapcheck  --edgefraction=1e-10 --nodefraction=1e-10 --text

image.gif编辑

好了,你可以看到这里已经很明显了,给你提示了t.cpp文件的第五行代码存在内存泄漏(当然你也可以输出其他格式,raw,png,pdf等等,whatever,只要可以帮助你去分析问题解决问题)。

实际上项目中遇到的内存泄漏问题是异常复杂的,我给的这个示例只是小试牛刀。项目常见的内存泄漏点大家都清楚,new了但是没有得到delete,但是要根据pprof工具对应的函数,代码行找到对应的泄漏点你可能需要花费点功夫。

实际上你的大多数应用都是以服务的方式启动,长时间处于作业/工作状态。你需要定期来检测下内存泄漏情况,那么这时你需要显示的调用接口来输出leak情况,

示例代码2

bool memory_check(void* arg)
{
    HeapLeakChecker::NoGlobalLeaks();
    return TRUE;
}

image.gif

将上面的代码加到你的定时检测逻辑里,或者需要观察的点,那么他就会输出示例1中的内容,动态的帮助你分析内存泄漏点。

分析使用tcmalloc后内存暴涨不降问题

记得几年前我开始推广大家使用tcmalloc后,一些同事做压测过程中也遇到了不少麻烦。比如当有大量数据过来,new出来很多的大块内存,突然发现有时候内存增长到几个G,开始以为是内存泄露的问题。

image.gif编辑

先是用tcmalloc环境变量来检查内存泄漏没有找到泄漏的报告,用valgrind也做了大量的测试,但是valgrind显示没有内存泄露。

实际上遇到这种问题不要慌,基本上是对tcmalloc使用上的问题,你要知道默认情况下,tcmalloc会将长时间未用的内存交还系统。tcmalloc_release_rate这个flag控制了这个交回频率。你可以在运行时通过这个语句强制这个release发生:

MallocExtension::instance()->ReleaseFreeMemory();

当然了,你可以通过 SetMemoryReleaseRate()  来设置这个tcmalloc_release_rate. 如果设置为0,代表永远不交回。数字越大代表交回的频率越大。一般合理的值就是设置一个0 - 10 之间的一个数。也可以通过设置环境变量 TCMALLOC_RELEASE_RATE来设置这个rate。

实际上我估计很多人看了官网说的

MallocExtension::instance()->SetMemoryReleaseRate(7.0);

很疑惑,我曾经带着疑惑做了测试,发现SetMemoryReleaseRate设置9,10回收的内存仍然是很慢的,所以后来我索性在进程启动的开始设置SetMemoryReleaseRate为9,然后在new对象的时候ReleaseFreeMemory,在new对象析构的时候ReleaseFreeMemory一次 (new出来的对象可能从new到delete的生命周期是不确定的,可能存在1天?4小时?30分钟都有可能,而且不是频繁的释放和销毁),因此这种情况下,内存就比较及时的回收了,所以大家可以根据自己的项目逻辑来选择ReleaseFreeMemory的时机,最好不要频繁的申请和释放,这对tcmalloc来说也是难受。

image.gif编辑

所以你不仅仅要关注tcmalloc申请大小内存块,还要关注内存块的在合适的时间及时回收,否则造成内存占用过高。

相关文章
|
3月前
|
存储 弹性计算 缓存
阿里云服务器ECS经济型、通用算力、计算型、通用和内存型选购指南及使用场景分析
本文详细解析阿里云ECS服务器的经济型、通用算力型、计算型、通用型和内存型实例的区别及适用场景,涵盖性能特点、配置比例与实际应用,助你根据业务需求精准选型,提升资源利用率并降低成本。
279 3
|
2月前
|
存储 人工智能 自然语言处理
AI代理内存消耗过大?9种优化策略对比分析
在AI代理系统中,多代理协作虽能提升整体准确性,但真正决定性能的关键因素之一是**内存管理**。随着对话深度和长度的增加,内存消耗呈指数级增长,主要源于历史上下文、工具调用记录、数据库查询结果等组件的持续积累。本文深入探讨了从基础到高级的九种内存优化技术,涵盖顺序存储、滑动窗口、摘要型内存、基于检索的系统、内存增强变换器、分层优化、图形化记忆网络、压缩整合策略以及类操作系统内存管理。通过统一框架下的代码实现与性能评估,分析了每种技术的适用场景与局限性,为构建高效、可扩展的AI代理系统提供了系统性的优化路径和技术参考。
154 4
AI代理内存消耗过大?9种优化策略对比分析
|
6月前
|
存储 Java
课时4:对象内存分析
接下来对对象实例化操作展开初步分析。在整个课程学习中,对象使用环节往往是最棘手的问题所在。
|
6月前
|
Java 编译器 Go
go的内存逃逸分析
内存逃逸分析是Go编译器在编译期间根据变量的类型和作用域,确定变量分配在堆上还是栈上的过程。如果变量需要分配在堆上,则称作内存逃逸。Go语言有自动内存管理(GC),开发者无需手动释放内存,但编译器需准确分配内存以优化性能。常见的内存逃逸场景包括返回局部变量的指针、使用`interface{}`动态类型、栈空间不足和闭包等。内存逃逸会影响性能,因为操作堆比栈慢,且增加GC压力。合理使用内存逃逸分析工具(如`-gcflags=-m`)有助于编写高效代码。
132 2
|
10月前
|
JavaScript
如何使用内存快照分析工具来分析Node.js应用的内存问题?
需要注意的是,不同的内存快照分析工具可能具有不同的功能和操作方式,在使用时需要根据具体工具的说明和特点进行灵活运用。
329 62
|
9月前
|
存储 监控 算法
Java内存管理深度剖析:从垃圾收集到内存泄漏的全面指南####
本文深入探讨了Java虚拟机(JVM)中的内存管理机制,特别是垃圾收集(GC)的工作原理及其调优策略。不同于传统的摘要概述,本文将通过实际案例分析,揭示内存泄漏的根源与预防措施,为开发者提供实战中的优化建议,旨在帮助读者构建高效、稳定的Java应用。 ####
156 35
|
10月前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
253 1
|
10月前
|
容器
在使用指针数组进行动态内存分配时,如何避免内存泄漏
在使用指针数组进行动态内存分配时,避免内存泄漏的关键在于确保每个分配的内存块都能被正确释放。具体做法包括:1. 分配后立即检查是否成功;2. 使用完成后及时释放内存;3. 避免重复释放同一内存地址;4. 尽量使用智能指针或容器类管理内存。
|
2月前
|
存储
阿里云轻量应用服务器收费标准价格表:200Mbps带宽、CPU内存及存储配置详解
阿里云香港轻量应用服务器,200Mbps带宽,免备案,支持多IP及国际线路,月租25元起,年付享8.5折优惠,适用于网站、应用等多种场景。
745 0
|
2月前
|
存储 缓存 NoSQL
内存管理基础:数据结构的存储方式
数据结构在内存中的存储方式主要包括连续存储、链式存储、索引存储和散列存储。连续存储如数组,数据元素按顺序连续存放,访问速度快但扩展性差;链式存储如链表,通过指针连接分散的节点,便于插入删除但访问效率低;索引存储通过索引表提高查找效率,常用于数据库系统;散列存储如哈希表,通过哈希函数实现快速存取,但需处理冲突。不同场景下应根据访问模式、数据规模和操作频率选择合适的存储结构,甚至结合多种方式以达到最优性能。掌握这些存储机制是构建高效程序和理解高级数据结构的基础。
212 0

热门文章

最新文章