亚洲视频二区_亚洲欧洲日本天天堂在线观看_日韩一区二区在线观看_中文字幕不卡一区

公告:魔扣目錄網(wǎng)為廣大站長(zhǎng)提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請(qǐng)做好本站友鏈:【 網(wǎng)站目錄:http://www.430618.com 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會(huì)員:747

記錄一個(gè)關(guān)于線程內(nèi)存泄漏問(wèn)題的定位過(guò)程,以及過(guò)程中的收獲。

1. 初步定位

是否存在內(nèi)存泄漏:想到內(nèi)存泄漏,首先查看/proc/meminfo,通過(guò)/proc/meminfo可以看出總體內(nèi)存在下降。確定內(nèi)存泄漏確實(shí)存在。top中可以顯示多種形式內(nèi)存,進(jìn)而可以判斷是那種泄漏。比如vss/rss/pss等。

確定哪個(gè)進(jìn)程內(nèi)存泄漏:通過(guò)top即可查看到是哪個(gè)進(jìn)程在泄漏。至此基本可以確定到哪個(gè)進(jìn)程。

確定進(jìn)程泄漏內(nèi)存類型:然后查看進(jìn)程的/proc/<pid>/maps,通過(guò)maps可以看出泄漏的內(nèi)存類型(堆、棧、匿名內(nèi)存等等),有時(shí)候運(yùn)氣好可以直接判斷泄漏點(diǎn)。

如果是slab:可以通過(guò)/proc/slabinfo,可以看出進(jìn)程的動(dòng)態(tài)變化情況。如果確定是哪一個(gè)slab,那么可以在/sys/kernel/slab/<slab name>/alloc_calls和free_calls中直接找到調(diào)用點(diǎn)。當(dāng)然看到的是內(nèi)核空間的函數(shù)。

使用mcheck():可以檢查malloc/free造成的泄漏問(wèn)題。

通過(guò)如下腳本,然后對(duì)每次抓取內(nèi)容進(jìn)行Beyond Compare。每個(gè)一定周期抓取相關(guān)內(nèi)存消耗信息。

#!/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

當(dāng)然還有其他工具gcc Sanitier、Valgrind等等,由于嵌入式環(huán)境受限未能使用。

2. 深入定位

同步查看meminfo、maps、slabinfo,發(fā)覺進(jìn)程虛擬內(nèi)存損耗很快,遠(yuǎn)比系統(tǒng)MemFree損耗快。而且slabinfo沒(méi)有和maps同步損耗。

所以問(wèn)題重點(diǎn)檢查maps問(wèn)題。

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]

通過(guò)多次maps對(duì)比,可以發(fā)現(xiàn)[stack:TID]類型的內(nèi)存以及一個(gè)匿名內(nèi)存在不停增加消耗內(nèi)存。

其中[stack:TID]類型的內(nèi)存,在內(nèi)核查找相關(guān)代碼沒(méi)有明確對(duì)應(yīng)屬性。初步判斷是線程的棧,TID表示線程id號(hào)。

所以這里應(yīng)該是某個(gè)線程泄漏。

2.1 線程棧泄漏(Joinable線程棧)

一個(gè)導(dǎo)致線程棧泄漏原因可能是對(duì)于一個(gè)Joinable線程,系統(tǒng)會(huì)創(chuàng)建線程私有的棧、threand ID、線程結(jié)束狀態(tài)等信息。

如果此線程沒(méi)有pthread_join(),那么系統(tǒng)不會(huì)對(duì)以上信息進(jìn)行回收。這就可能造成線程棧等泄漏。

確定線程棧泄漏的方法是:通過(guò)ls /proc/<pid>/task | wc -l確定進(jìn)程下線程數(shù)目。然后在maps中檢查[stack:TID]數(shù)目。兩者如果不一致,則存在Joinable線程沒(méi)有調(diào)用pthread_join()造成的泄漏。

如果maps沒(méi)有[stack:TID],可以通過(guò)pmap <pid> | grep <stack size> | wc -l,即通過(guò)檢查棧大小的vma數(shù)目來(lái)確定棧數(shù)目。

3. 問(wèn)題根源

通過(guò)檢查線程棧消耗與實(shí)際線程數(shù)目,發(fā)現(xiàn)兩者數(shù)目吻合。所以線程并沒(méi)有退出。也即不是由于未使用pthread_join()導(dǎo)致的內(nèi)存泄漏。

然后根據(jù)maps中[stack:TID]的pid號(hào),cat /proc/<pid>/comm發(fā)現(xiàn)是同一個(gè)線程不停創(chuàng)建。但是沒(méi)有釋放。

其實(shí)通過(guò)top -H -p <pid>和maps也可發(fā)現(xiàn)問(wèn)題,中間走了彎路。

所以問(wèn)題的根源是,進(jìn)程不停創(chuàng)建但是沒(méi)有退出造成內(nèi)存消耗殆盡。

相關(guān)視頻推薦

內(nèi)存泄漏的3個(gè)解決方案與原理實(shí)現(xiàn),知道一個(gè)可以輕松應(yīng)對(duì)開發(fā)

4種實(shí)時(shí)線上內(nèi)存泄漏檢測(cè)的實(shí)現(xiàn)方式【linux后臺(tái)開發(fā)】

學(xué)習(xí)地址:C/C++Linux服務(wù)器開發(fā)/后臺(tái)架構(gòu)師【零聲教育】-學(xué)習(xí)視頻教程-騰訊課堂

需要C/C++ linux服務(wù)器架構(gòu)師學(xué)習(xí)資料加qun812855908獲?。ㄙY料包括C/C++,Linux,golang技術(shù),Nginx,ZeroMQ,MySQL,redis,fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,TCP/IP,協(xié)程,DPDK,ffmpeg等),免費(fèi)分享

一個(gè)線程內(nèi)存泄漏問(wèn)題定位過(guò)程

 

4. 收獲

有兩個(gè)收獲,一是創(chuàng)建的pthread線程Join和Detach兩種狀態(tài)下內(nèi)存處理差別;二是在進(jìn)程maps中顯示線程棧[stack:TID]更有利于調(diào)試。

4.1 pthread線程的join和detach區(qū)別

《Avoiding memory leaks in POSIX thread programming》講到如何避免POSIX線程編程時(shí)內(nèi)存泄漏。

首先pthread_create()創(chuàng)建的線程默認(rèn)是joinable的。

對(duì)于joinable線程,系統(tǒng)會(huì)分配私有內(nèi)存存儲(chǔ)線程結(jié)束狀態(tài)、線程棧、線程ID等等資源。這些資源會(huì)一直存在,直到線程結(jié)束并且線程被其他線程joined。所以確保joinable線程資源得到釋放的兩個(gè)條件是:線程退出、被其他線程joined。

對(duì)于detached線程,如果其退出,那么系統(tǒng)會(huì)自動(dòng)回收其占用的資源。

關(guān)于joinable線程沒(méi)有被其他線程joined造成內(nèi)存泄漏的實(shí)驗(yàn)。

#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;
}

輸出結(jié)果如下:

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

總共創(chuàng)建了32571個(gè)線程,造成內(nèi)存消耗殆盡。

通過(guò)對(duì)比中間過(guò)程的maps,可以發(fā)現(xiàn)每次增加一個(gè)8MB的棧以及一個(gè)分隔頁(yè)。

一個(gè)線程內(nèi)存泄漏問(wèn)題定位過(guò)程

 

在pthread_create()之后增加pthread_join()則內(nèi)存非常穩(wěn)定。

#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;
}

借用文檔里面一句話總結(jié)一下: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.

調(diào)用pthread_join()將阻塞線程自己,一直等到加入的線程運(yùn)行結(jié)束。

線程可以分為兩種:joined和detached。并不是所有線程創(chuàng)建后都默認(rèn)joinable,需要顯式指定屬性。

joinable線程在創(chuàng)建后,可以通過(guò)pthread_detach()顯式分離。在分離后,不可以再合并。

如果一個(gè)線程結(jié)束運(yùn)行,但沒(méi)有被join。則它的狀態(tài)類似進(jìn)程中的Zombie Process,即還有一部分資源沒(méi)有被回收,所以創(chuàng)建線程者應(yīng)該調(diào)用pthread_join()來(lái)等待線程結(jié)束,并可得到線程的退出代碼,回收其資源。

如果父進(jìn)程調(diào)用pthread_detach(child_thread_id)或者子進(jìn)程調(diào)用pthread_detack(pthread_self())即可將子進(jìn)程狀態(tài)設(shè)置為detached,該程序運(yùn)行結(jié)束后會(huì)自動(dòng)釋放所有資源。

4.2 關(guān)于在maps中顯示[stack:TID]

在進(jìn)程maps中顯示線程棧信息,最后在內(nèi)核中被放棄。

首先在《procfs: mark thread stack correctly in proc/<pid>/maps》中,添加了[stack:TID]用于表示此vma對(duì)應(yīng)的是線程TID的stack區(qū)域。

這樣做的好處是,可以從maps中明確知道此段vma是被哪個(gè)線程使用的。

有一個(gè)壞處就是先線程非常多情況下,主線程中為了顯示[stack:TIS],開銷就會(huì)很大,而實(shí)際上用處不是很大。

所以在《proc: revert /proc/<pid>/maps [stack:TID] annotation》將進(jìn)程maps中的[stack:TID]刪除了,只顯示為匿名內(nèi)存。

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

那么在沒(méi)有[stack:TID]的情況下如何斷定vma是否是線程棧呢?

首先線程棧大小可以通過(guò)ulimit -s查看,所以maps中vma大小和這個(gè)一致;并且屬性應(yīng)該是匿名的rw-p。

然后上面應(yīng)該是一頁(yè)大小作為分隔區(qū)間,分隔頁(yè)的屬性應(yīng)該是---p。

分享到:
標(biāo)簽:泄漏 內(nèi)存
用戶無(wú)頭像

網(wǎng)友整理

注冊(cè)時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

趕快注冊(cè)賬號(hào),推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過(guò)答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫(kù),初中,高中,大學(xué)四六

運(yùn)動(dòng)步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動(dòng)步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績(jī)?cè)u(píng)定2018-06-03

通用課目體育訓(xùn)練成績(jī)?cè)u(píng)定