您当前的位置:首页 > 电脑百科 > 电脑知识

一个线程内存泄漏问题定位过程

时间:2022-04-26 10:04:43  来源:  作者:linux技术栈

记录一个关于线程内存泄漏问题的定位过程,以及过程中的收获。

1. 初步定位

是否存在内存泄漏:想到内存泄漏,首先查看/proc/meminfo,通过/proc/meminfo可以看出总体内存在下降。确定内存泄漏确实存在。top中可以显示多种形式内存,进而可以判断是那种泄漏。比如vss/rss/pss等。

确定哪个进程内存泄漏:通过top即可查看到是哪个进程在泄漏。至此基本可以确定到哪个进程。

确定进程泄漏内存类型:然后查看进程的/proc/<pid>/maps,通过maps可以看出泄漏的内存类型(堆、栈、匿名内存等等),有时候运气好可以直接判断泄漏点。

如果是slab:可以通过/proc/slabinfo,可以看出进程的动态变化情况。如果确定是哪一个slab,那么可以在/sys/kernel/slab/<slab name>/alloc_calls和free_calls中直接找到调用点。当然看到的是内核空间的函数。

使用mcheck():可以检查malloc/free造成的泄漏问题。

通过如下脚本,然后对每次抓取内容进行Beyond Compare。每个一定周期抓取相关内存消耗信息。

#!/bin/bash
echo > mem_log.txt
while true
do
    cat /proc/meminfo >>mem_log.txt
    cat /proc/<pid>/maps >>mem_log.txt
    cat /proc/slabinfo >>mem_log.txt
    sleep 240
done

当然还有其他工具gcc Sanitier、Valgrind等等,由于嵌入式环境受限未能使用。

2. 深入定位

同步查看meminfo、maps、slabinfo,发觉进程虚拟内存损耗很快,远比系统MemFree损耗快。而且slabinfo没有和maps同步损耗。

所以问题重点检查maps问题。

00010000-00083000 r-xp 00000000 b3:11 22         /heop/package/AIApp/AiApp
00092000-00099000 rwxp 00072000 b3:11 22         /heop/package/AiApp/AiApp
00099000-00b25000 rwxp 00000000 00:00 0          [heap]
00b51000-00b52000 ---p 00000000 00:00 0 
00b52000-01351000 rwxp 00000000 00:00 0          [stack:30451]
01351000-01352000 ---p 00000000 00:00 0 
01352000-01b51000 rwxp 00000000 00:00 0 
01b51000-01b52000 ---p 00000000 00:00 0 
01b52000-02351000 rwxp 00000000 00:00 0          [stack:30432]
02351000-02352000 ---p 00000000 00:00 0 
02352000-02b51000 rwxp 00000000 00:00 0 
02b51000-02b52000 ---p 00000000 00:00 0 
...
64f55000-65754000 rwxp 00000000 00:00 0          [stack:28646]
65754000-65755000 ---p 00000000 00:00 0 
65755000-65f54000 rwxp 00000000 00:00 0          [stack:28645]
65f54000-65f55000 ---p 00000000 00:00 0 
65f55000-66754000 rwxp 00000000 00:00 0          [stack:28642]
66754000-6675a000 r-xp 00000000 00:02 5000324    /usr/lib/AiApp/gstreamer-1.0/libgsticcsink.so
6675a000-66769000 ---p 00000000 00:00 0 
...
6699f000-669a0000 rwxp 00000000 00:02 4999516    /usr/lib/AiApp/gstreamer-1.0/libgstapp.so
669a0000-66a2e000 rwxp 00000000 00:02 4999517    /usr/lib/AiApp/gstreamer-1.0/libgstlive555src.so
66a2e000-66a3e000 ---p 00000000 00:00 0 
66a3e000-66a44000 rwxp 0008e000 00:02 4999517    /usr/lib/AiApp/gstreamer-1.0/libgstlive555src.so
66a44000-66a45000 rwxp 00000000 00:00 0 
66a45000-66a46000 ---p 00000000 00:00 0 
66a46000-67245000 rwxp 00000000 00:00 0          [stack:28631]
67245000-67246000 ---p 00000000 00:00 0 
67246000-67a45000 rwxp 00000000 00:00 0          [stack:28630]
...
6b245000-6b246000 ---p 00000000 00:00 0 
6b246000-6ba45000 rwxp 00000000 00:00 0          [stack:28613]
6ba45000-6ba46000 ---p 00000000 00:00 0 
6ba46000-6c245000 rwxp 00000000 00:00 0          [stack:28610]
6c245000-71066000 rwxs 00000000 00:01 196614     /SYSV5553fc99 (deleted)
71066000-71067000 ---p 00000000 00:00 0 
71067000-71866000 rwxp 00000000 00:00 0          [stack:28609]
71866000-71867000 ---p 00000000 00:00 0 
71867000-72066000 rwxp 00000000 00:00 0          [stack:28608]
72066000-72228000 rwxs e3dc4000 00:02 6918       /dev/mmz_userdev
72228000-725ac000 rwxs e3a40000 00:02 6918       /dev/mmz_userdev
725ac000-75cac000 rwxs 00000000 00:01 131076     /SYSV6702121c (deleted)
75cac000-75e8a000 rwxs 00000000 00:01 98307      /SYSV6602121c (deleted)
75e8a000-7608e000 rwxp 00000000 00:00 0...
76eeb000-76efb000 ---p 00000000 00:00 0 
76efb000-76eff000 r-xp 000ce000 00:02 1234       /lib/libstdc++.so.6.0.20
76eff000-76f01000 rwxp 000d2000 00:02 1234       /lib/libstdc++.so.6.0.20
76f01000-76f08000 rwxp 00000000 00:00 0 
76f08000-76f0f000 r-xp 00000000 00:02 1235       /lib/ld-uClibc-0.9.33.2.so
76f1a000-76f1e000 rwxp 00000000 00:00 0 
76f1e000-76f1f000 rwxp 00006000 00:02 1235       /lib/ld-uClibc-0.9.33.2.so
76f1f000-76f20000 ---p 00000000 00:00 0...
7c720000-7cf1f000 rwxp 00000000 00:00 0          [stack:30574]
7cf1f000-7cf20000 ---p 00000000 00:00 0 
7cf20000-7e121000 rwxp 00000000 00:00 0          [stack:30575]
7eef7000-7ef18000 rwxp 00000000 00:00 0          [stack]
7efb7000-7efb8000 r-xp 00000000 00:00 0          [sigpage]
ffff0000-ffff1000 r-xp 00000000 00:00 0          [vectors]

通过多次maps对比,可以发现[stack:TID]类型的内存以及一个匿名内存在不停增加消耗内存。

其中[stack:TID]类型的内存,在内核查找相关代码没有明确对应属性。初步判断是线程的栈,TID表示线程id号。

所以这里应该是某个线程泄漏。

2.1 线程栈泄漏(Joinable线程栈)

一个导致线程栈泄漏原因可能是对于一个Joinable线程,系统会创建线程私有的栈、threand ID、线程结束状态等信息。

如果此线程没有pthread_join(),那么系统不会对以上信息进行回收。这就可能造成线程栈等泄漏。

确定线程栈泄漏的方法是:通过ls /proc/<pid>/task | wc -l确定进程下线程数目。然后在maps中检查[stack:TID]数目。两者如果不一致,则存在Joinable线程没有调用pthread_join()造成的泄漏。

如果maps没有[stack:TID],可以通过pmap <pid> | grep <stack size> | wc -l,即通过检查栈大小的vma数目来确定栈数目。

3. 问题根源

通过检查线程栈消耗与实际线程数目,发现两者数目吻合。所以线程并没有退出。也即不是由于未使用pthread_join()导致的内存泄漏。

然后根据maps中[stack:TID]的pid号,cat /proc/<pid>/comm发现是同一个线程不停创建。但是没有释放。

其实通过top -H -p <pid>和maps也可发现问题,中间走了弯路。

所以问题的根源是,进程不停创建但是没有退出造成内存消耗殆尽。

相关视频推荐

内存泄漏的3个解决方案与原理实现,知道一个可以轻松应对开发

4种实时线上内存泄漏检测的实现方式【linux后台开发】

学习地址:C/C++Linux服务器开发/后台架构师【零声教育】-学习视频教程-腾讯课堂

需要C/C++ linux服务器架构师学习资料加qun812855908获取(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQLredis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享

一个线程内存泄漏问题定位过程

 

4. 收获

有两个收获,一是创建的pthread线程Join和Detach两种状态下内存处理差别;二是在进程maps中显示线程栈[stack:TID]更有利于调试。

4.1 pthread线程的join和detach区别

《Avoiding memory leaks in POSIX thread programming》讲到如何避免POSIX线程编程时内存泄漏。

首先pthread_create()创建的线程默认是joinable的。

对于joinable线程,系统会分配私有内存存储线程结束状态、线程栈、线程ID等等资源。这些资源会一直存在,直到线程结束并且线程被其他线程joined。所以确保joinable线程资源得到释放的两个条件是:线程退出、被其他线程joined。

对于detached线程,如果其退出,那么系统会自动回收其占用的资源。

关于joinable线程没有被其他线程joined造成内存泄漏的实验。

#include<stdio.h>
#include<pthread.h>

void run() {
   pthread_exit(0);
}

int main () {
    pthread_t thread;
    int rc;
    long count = 0;
    while(1) {
        if(rc = pthread_create(&thread, 0, run, 0) ) {
            printf("ERROR, rc is %d, so far %ld threads createdn", rc, count);
            perror("Fail:");
            return -1;
        }
        usleep(10);
        count++;
    }
    return 0;
}

输出结果如下:

ERROR, rc is 11, so far 32751 threads created
Fail:: Cannot allocate memory

总共创建了32571个线程,造成内存消耗殆尽。

通过对比中间过程的maps,可以发现每次增加一个8MB的栈以及一个分隔页。

一个线程内存泄漏问题定位过程

 

在pthread_create()之后增加pthread_join()则内存非常稳定。

#include<stdio.h>
#include<pthread.h>

void run() {
   pthread_exit(0);
}

int main () {
    pthread_t thread;
    int rc;
    long count = 0;
    while(1) {
        if(rc = pthread_create(&thread, 0, run, 0) ) {
            printf("ERROR, rc is %d, so far %ld threads createdn", rc, count);
            perror("Fail:");
            return -1;
        }
        pthread_join(thread, NULL);
        usleep(10);
        count++;
    }
    return 0;
}

借用文档里面一句话总结一下:Joinable threads should be joined during programming. If you are creating joinable threads in your program, don’t forget to call pthread_join(pthread_t, void**) to recycle the private storage allocated to the thread.

调用pthread_join()将阻塞线程自己,一直等到加入的线程运行结束。

线程可以分为两种:joined和detached。并不是所有线程创建后都默认joinable,需要显式指定属性。

joinable线程在创建后,可以通过pthread_detach()显式分离。在分离后,不可以再合并。

如果一个线程结束运行,但没有被join。则它的状态类似进程中的Zombie Process,即还有一部分资源没有被回收,所以创建线程者应该调用pthread_join()来等待线程结束,并可得到线程的退出代码,回收其资源。

如果父进程调用pthread_detach(child_thread_id)或者子进程调用pthread_detack(pthread_self())即可将子进程状态设置为detached,该程序运行结束后会自动释放所有资源。

4.2 关于在maps中显示[stack:TID]

在进程maps中显示线程栈信息,最后在内核中被放弃。

首先在《procfs: mark thread stack correctly in proc/<pid>/maps》中,添加了[stack:TID]用于表示此vma对应的是线程TID的stack区域。

这样做的好处是,可以从maps中明确知道此段vma是被哪个线程使用的。

有一个坏处就是先线程非常多情况下,主线程中为了显示[stack:TIS],开销就会很大,而实际上用处不是很大。

所以在《proc: revert /proc/<pid>/maps [stack:TID] annotation》将进程maps中的[stack:TID]删除了,只显示为匿名内存。

最终再《fs/proc: Stop trying to report thread stacks》将所有[stack:TID]全部移除。

那么在没有[stack:TID]的情况下如何断定vma是否是线程栈呢?

首先线程栈大小可以通过ulimit -s查看,所以maps中vma大小和这个一致;并且属性应该是匿名的rw-p。

然后上面应该是一页大小作为分隔区间,分隔页的属性应该是---p。



Tags:内存泄漏   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
记一次Rust内存泄漏排查之旅
在某次持续压测过程中,我们发现 GreptimeDB 的 Frontend 节点内存即使在请求量平稳的阶段也在持续上涨,直至被 OOM kill。我们判断 Frontend 应该是有内存泄漏了,于是开启了排...【详细内容】
2024-02-27  Search: 内存泄漏  点击:(15)  评论:(0)  加入收藏
在项目中如何避免和解决Java内存泄漏问题
在Java中,内存泄漏通常指的是程序中存在一些不再使用的对象或数据结构仍然保持对内存的引用,从而导致这些对象无法被垃圾回收器回收,最终导致内存占用不断增加,进而影响程序的性...【详细内容】
2024-02-01  Search: 内存泄漏  点击:(77)  评论:(0)  加入收藏
何为内存泄漏?如何监测并防止内存泄漏事故发生
本文关键要点: 1)当应用程序无法返回分配的内存时,就会发生内存泄漏,逐渐消耗更多内存并可能导致系统崩溃。2)用户可以通过监控系统的 RAM 使用情况来识别任何稳定消耗更多内存的...【详细内容】
2023-12-18  Search: 内存泄漏  点击:(72)  评论:(0)  加入收藏
Go 语言中的map和内存泄漏
Map在内存中总是会增长;它不会收缩。因此,如果map导致了一些内存问题,你可以尝试不同的选项,比如强制 Go 重新创建map或使用指针。在 Go 中使用map时,我们需要了解map增长和收缩...【详细内容】
2023-11-23  Search: 内存泄漏  点击:(249)  评论:(0)  加入收藏
Android使用LeakCanary检测内存泄漏
Java四种引用在Java中,有四种不同的引用类型,分别是强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)和虚引用(Phantom Reference)。 强引用(Strong Refere...【详细内容】
2023-11-01  Search: 内存泄漏  点击:(219)  评论:(0)  加入收藏
如何避免Java内存泄漏,来看看这个
引言:在Java应用程序开发中,内存泄漏是一个常见而严重的问题。本文将帮助Java开发人员和软件工程师了解内存泄漏的危害,并提供解决方案。了解内存泄漏: 内存泄漏是指分配的内存...【详细内容】
2023-10-30  Search: 内存泄漏  点击:(244)  评论:(0)  加入收藏
如何避免JavaScript中的内存泄漏?
作者 | 葡萄城技术团队原文链接:https://my.oschina.net/powertoolsteam/blog/10122640前言过去,我们浏览静态网站时无须过多关注内存管理,因为加载新页面时,之前的页面信息会从...【详细内容】
2023-10-27  Search: 内存泄漏  点击:(115)  评论:(0)  加入收藏
大厂面试必问:内存泄漏和内存溢出的区别?
程序的运行需要内存。只要程序提出要求,操作系统或者运行时就必须供给内存。对于持续运行的服务进程,必须及时释放不再用到的内存。否则,内存占用越来越高,轻则影响系统性能,重则...【详细内容】
2023-05-26  Search: 内存泄漏  点击:(179)  评论:(0)  加入收藏
C/C++中内存泄漏的三种情况分析
内存泄漏是指一块动态分配的内存没有释放,同时又失去了所有对它的引用。在C语言中,这种情况通常发生在使用函数(如malloc或calloc)动态分配内存时,没有使用free函数来释放不再需...【详细内容】
2023-04-13  Search: 内存泄漏  点击:(112)  评论:(0)  加入收藏
深入了解 JavaScript 内存泄漏
作者:京东零售 谢天在任何语言开发的过程中,对于内存的管理都非常重要,Javascript 也不例外。然而在前端浏览器中,用户一般不会在一个页面停留很久,即使有一点内存泄漏,重新加载页...【详细内容】
2023-03-23  Search: 内存泄漏  点击:(149)  评论:(0)  加入收藏
▌简易百科推荐
云计算和边缘计算
云计算和边缘计算是两种不同的计算模型,它们各有特点,适用于不同的场景和需求。云计算是一种基于互联网的计算模型,它将计算资源、存储资源和应用服务集中在云端,用户可以通过网...【详细内容】
2024-03-01    简易百科  Tags:云计算   点击:(34)  评论:(0)  加入收藏
云计算与边缘计算:有何不同?
公共云计算平台可以帮助企业充分利用全球服务器来增强其私有数据中心。这使得基础设施能够扩展到任何位置,并有助于计算资源的灵活扩展。混合公共-私有云为企业计算应用程序...【详细内容】
2024-02-28  通信产品推荐官    Tags:云计算   点击:(24)  评论:(0)  加入收藏
量子计算机是什么?跟现在的计算机相比优缺点是什么?
量子计算机是什么?跟现在的计算机相比优缺点是什么? 随着科技的不断发展,计算机技术也取得了巨大的进步。然而,随着摩尔定律的趋近于极限,传统的计算机技术面临着许多挑战。这时...【详细内容】
2024-02-23    简易百科  Tags:量子计算机   点击:(45)  评论:(0)  加入收藏
量子计算机:未来电脑的革命性技术
在科技的广袤天空中,量子计算机如一颗璀璨的新星,以其独特的光芒预示着未来电脑的革命性变革。这项令人瞩目的技术不仅代表着计算机科学的最前沿,更承载着人类对于速度和效率的...【详细内容】
2024-02-23  小浩长得帅    Tags:量子计算机   点击:(50)  评论:(0)  加入收藏
为什么计算机需要十六进制?
今天简单聊聊十六进制。实际上计算机本身是不需要十六进制的,计算机只需要二进制,需要十六进制的是人。每个十六进制中的数字代表4个比特,你可以非常直观的从十六进制中知道对...【详细内容】
2024-02-22  码农的荒岛求生  微信公众号  Tags:计算机   点击:(54)  评论:(0)  加入收藏
多模态RAG应用:跨越文本与图片的智能交互
近年来,多模态RAG(Retrieval-AugmentedGeneration)应用的兴起引发了人们对人工智能技术发展方向的广泛关注。传统的RAG应用主要基于文本的输入和输出,而随着GPT4-V的发布,多模态R...【详细内容】
2024-01-29  况成放    Tags:多模态RAG   点击:(84)  评论:(0)  加入收藏
量子计算机真相揭秘,一篇文章颠覆你的认知
你看过《三体》吗?在刘慈欣笔下,三体人用一种叫“智子”的黑科技干扰了人类的实验,从而锁死了人类的技术。而在现实世界,一把无形的“锁”其实也悄然逼近了我们,它就是芯片。随着...【详细内容】
2024-01-23  天才简史  今日头条  Tags:量子计算机   点击:(28)  评论:(0)  加入收藏
生成对抗网络(GAN)在计算机视觉领域中的应用
生成对抗网络(GAN)是一种在计算机视觉领域中广泛应用的深度学习模型。它由一个生成器网络和一个判别器网络组成,通过对抗训练的方式实现图像的生成和判别。GAN在计算机视觉中的...【详细内容】
2024-01-15  数码小风向    Tags:生成对抗网络   点击:(81)  评论:(0)  加入收藏
如何免费生成logo?
Logo设计对于一个品牌来说非常重要,它是品牌的身份标识,可以帮助人们迅速识别和记住一个品牌。同时还可以帮助建立品牌认知度,传达出品牌的专业形象。无论是大公司还是刚起步的...【详细内容】
2024-01-05  阳仔问文    Tags:logo   点击:(100)  评论:(0)  加入收藏
自然语言处理中的句法分析方法研究与实现
自然语言处理(NLP)中的句法分析方法是NLP领域的重要研究内容之一,它旨在通过对句子结构的分析和理解,揭示句子中单词之间的语法关系,为后续的语义理解和信息提取提供基础支撑。本...【详细内容】
2024-01-04  毛晓峰    Tags:自然语言处理   点击:(61)  评论:(0)  加入收藏
站内最新
站内热门
站内头条