在线观看www成人影院-在线观看www日本免费网站-在线观看www视频-在线观看操-欧美18在线-欧美1级

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

內(nèi)存是如何泄露的

科技綠洲 ? 來源:Linux開發(fā)架構(gòu)之路 ? 作者:Linux開發(fā)架構(gòu)之路 ? 2023-11-13 14:13 ? 次閱讀

作為 C++ 程序員,內(nèi)存泄露始終是懸在頭上的一顆炸彈。在過去幾年的 C++ 開發(fā)過程中,由于我們采用了一些技術(shù),我們的程序發(fā)生內(nèi)存泄露的情況屈指可數(shù)。今天就在這里向大家做一個(gè)簡(jiǎn)單的介紹。

內(nèi)存是如何泄露的

在 C++ 程序中,主要涉及到的內(nèi)存就是『棧』和『堆』(其他部分不在本文中介紹了)。

圖片

通常來說,一個(gè)線程的棧內(nèi)存是有限的,通常來說是 8M 左右(取決于運(yùn)行的環(huán)境)。棧上的內(nèi)存通常是由編譯器來自動(dòng)管理的。當(dāng)在棧上分配一個(gè)新的變量時(shí),或進(jìn)入一個(gè)函數(shù)時(shí),棧的指針會(huì)下移,相當(dāng)于在棧上分配了一塊內(nèi)存。我們把一個(gè)變量分配在棧上,也就是利用了棧上的內(nèi)存空間。當(dāng)這個(gè)變量的生命周期結(jié)束時(shí),棧的指針會(huì)上移,相同于回收了內(nèi)存。

由于棧上的內(nèi)存的分配和回收都是由編譯器控制的,所以在棧上是不會(huì)發(fā)生內(nèi)存泄露的,只會(huì)發(fā)生棧溢出(Stack Overflow),也就是分配的空間超過了規(guī)定的棧大小。

而堆上的內(nèi)存是由程序直接控制的,程序可以通過 malloc/free 或 new/delete 來分配和回收內(nèi)存,如果程序中通過 malloc/new 分配了一塊內(nèi)存,但忘記使用 free/delete 來回收內(nèi)存,就發(fā)生了內(nèi)存泄露。

經(jīng)驗(yàn) #1:盡量避免在堆上分配內(nèi)存

既然只有堆上會(huì)發(fā)生內(nèi)存泄露,那第一原則肯定是避免在堆上面進(jìn)行內(nèi)存分配,盡可能的使用棧上的內(nèi)存,由編譯器進(jìn)行分配和回收,這樣當(dāng)然就不會(huì)有內(nèi)存泄露了。

然而,只在棧上分配內(nèi)存,在有 IO 的情況下是存在一定局限性的。

舉個(gè)例子,為了完成一個(gè)請(qǐng)求,我們通常會(huì)為這個(gè)請(qǐng)求構(gòu)造一個(gè) Context 對(duì)象,用于描述和這個(gè)請(qǐng)求有關(guān)的一些上下文。例如下面一段代碼:

void Foo(Reuqest* req) {
    RequestContext ctx(req);
    HandleRequest(&ctx);
}

如果 HandleRequest 是一個(gè)同步函數(shù),當(dāng)這個(gè)函數(shù)返回時(shí),請(qǐng)求就可以被處理完成,那么顯然 ctx 是可以被分配在棧上的。

但如果 HandleRequest 是一個(gè)異步函數(shù),例如:

void HandleRequest(RequestContext* ctx, Callback cb);

那么顯然,ctx 是不能被分配在棧上的,因?yàn)槿绻?ctx 被分配在棧上,那么當(dāng) Foo 函數(shù)推出后,ctx 對(duì)象的生命周期也就結(jié)束了。而 FooCB 中顯然會(huì)使用到 ctx 對(duì)象。

void HandleRequest(RequestContext* ctx, Callback cb);

void Foo(Reuqest* req) {
    auto ctx = new RequestContext(req);
    HandleRequest(ctx, FooCB);
}

void FooCB(RequestContext* ctx) {
    FinishRequest(ctx);
    delete ctx;
}

在這種情況下,如果忘記在 FooCB 中調(diào)用 delete ctx,則就會(huì)觸發(fā)內(nèi)存泄露。盡管我們可以借助一些靜態(tài)檢查工具對(duì)代碼進(jìn)行檢查,但往往異步程序的邏輯是極其復(fù)雜的,一個(gè)請(qǐng)求的生命周期中,也需要進(jìn)行大量的內(nèi)存分配操作,靜態(tài)檢查工具往往無(wú)法發(fā)現(xiàn)所有的內(nèi)存泄露情況。

那么怎么才能避免這種情況的產(chǎn)生呢?引入智能指針顯然是一種可行的方法,但引入 shared_ptr 往往引入了額外的性能開銷,并不十分理想。

在 SmartX,我們通常采用兩種方法來應(yīng)對(duì)這種情況。

經(jīng)驗(yàn) #2:使用 Arena

Arena 是一種統(tǒng)一化管理內(nèi)存生命周期的方法。所有需要在堆上分配的內(nèi)存,不通過 malloc/new,而是通過 Arena 的 CreateObject 接口。同時(shí),不需要手動(dòng)的執(zhí)行 free/delete,而是在 Arena 被銷毀的時(shí)候,統(tǒng)一釋放所有通過 Arena 對(duì)象申請(qǐng)的內(nèi)存。所以,只需要確保 Arena 對(duì)象一定被銷毀就可以了,而不用再關(guān)心其他對(duì)象是否有漏掉的 free/delete。這樣顯然降低了內(nèi)存管理的復(fù)雜度。

此外,我們還可以將 Arena 的生命周期與 Request 的生命周期綁定,一個(gè) Request 生命周期內(nèi)的所有內(nèi)存分配都通過 Arena 完成。這樣的好處是,我們可以在構(gòu)造 Arena 的時(shí)候,大概預(yù)估出處理完成這個(gè) Request 會(huì)消耗多少內(nèi)存,并提前將會(huì)使用到的內(nèi)存一次性的申請(qǐng)完成,從而減少了在處理一個(gè)請(qǐng)求的過程中,分配和回收內(nèi)存的次數(shù),從而優(yōu)化了性能。

我們最早看到 Arena 的思想,是在 LevelDB 的代碼中。這段代碼相當(dāng)簡(jiǎn)單,建議大家直接閱讀。

經(jīng)驗(yàn) #3:使用 Coroutine

Coroutine 相信大家并不陌生,那 Coroutine 的本質(zhì)是什么?我認(rèn)為 Coroutine 的本質(zhì),是使得一個(gè)線程中可以存在多個(gè)上下文,并可以由用戶控制在多個(gè)上下文之間進(jìn)行切換。而在上下文中,一個(gè)重要的組成部分,就是棧指針。使用 Coroutine,意味著我們?cè)谝粋€(gè)線程中,可以創(chuàng)造(或模擬)多個(gè)棧。

有了多個(gè)棧,意味著當(dāng)我們要做一個(gè)異步處理時(shí),不需要釋放當(dāng)前棧上的內(nèi)存,而只需要切換到另一個(gè)棧上,就可以繼續(xù)做其他的事情了,當(dāng)異步處理完成時(shí),可以再切換回到這個(gè)棧上,將這個(gè)請(qǐng)求處理完成。

還是以剛才的代碼為示例:

void Foo(Reuqest* req) {
    RequestContext ctx(req);
    HandleRequest(&ctx);
}

void HandleRequest(RequestCtx* ctx) {
    SubmitAsync(ctx);
    Coroutine::Self()- >Yield();
    CompleteRequest(ctx);
}

這里的精髓在于,盡管 Coroutine::Self()->Yield() 被調(diào)用時(shí),程序可以跳出 HandleRequest 函數(shù)去執(zhí)行其他代碼邏輯,但當(dāng)前的棧卻被保存了下來,所以 ctx 對(duì)象是安全的,并沒有被釋放。

這樣一來,我們就可以完全拋棄在堆上申請(qǐng)內(nèi)存,只是用棧上的內(nèi)存,就可以完成請(qǐng)求的處理,完全不用考慮內(nèi)存泄露的問題。然而這種假設(shè)過于理想,由于在棧上申請(qǐng)內(nèi)存存在一定的限制,例如棧大小的限制,以及需要在編譯是知道分配內(nèi)存的大小,所以在實(shí)際場(chǎng)景中,我們通常會(huì)結(jié)合使用 Arena 和 Coroutine 兩種技術(shù)一起使用。

有人可能會(huì)提到,想要多個(gè)棧用多個(gè)線程不就可以了?然而用多線程實(shí)現(xiàn)多個(gè)棧的問題在于,線程的創(chuàng)建和銷毀的開銷極大,且線程間切塊,也就是在棧之間進(jìn)行切換的代銷需要經(jīng)過操作系統(tǒng),這個(gè)開銷也是極大的。所以想用線程模擬多個(gè)棧的想法在實(shí)際場(chǎng)景中是走不通的。

關(guān)于 Coroutine 有很多開源的實(shí)現(xiàn)方式,大家可以在 github 上找到很多,C++20 標(biāo)準(zhǔn)也會(huì)包含 Coroutine 的支持。在 SmartX 內(nèi)部,我們很早就實(shí)現(xiàn)了 Coroutine,并對(duì)所有異步 IO 操作進(jìn)行了封裝,示例可參考我們之前的一篇文章 smartx:基于 Coroutine 的異步 RPC 框架示例(C++)

這里需要強(qiáng)調(diào)一下,Coroutine 確實(shí)會(huì)帶來一定的性能開銷,通常 Coroutine 切換的開銷在 20ns 以內(nèi),然而我們依然在對(duì)性能要求很苛刻的場(chǎng)景使用 Coroutine,一方面是因?yàn)?20ns 的性能開銷是相對(duì)很小的,另一方面是因?yàn)?Coroutine 極大的降低了異步編程的復(fù)雜度,降低了內(nèi)存泄露的可能性,使得編寫異步程序像編寫同步程序一樣簡(jiǎn)單,降低了程序員心智的開銷。

圖片

經(jīng)驗(yàn) #4:善用 RAII

盡管在有些場(chǎng)景使用了 Coroutine,但還是可能會(huì)有在堆上申請(qǐng)內(nèi)存的需要,而此時(shí)有可能 Arena 也并不適用。在這種情況下,善用 RAII(Resource Acquisition Is Initialization)思想會(huì)幫助我們解決很多問題。

簡(jiǎn)單來說,RAII 可以幫助我們將管理堆上的內(nèi)存,簡(jiǎn)化為管理?xiàng)I系膬?nèi)存,從而達(dá)到利用編譯器自動(dòng)解決內(nèi)存回收問題的效果。此外,RAII 可以簡(jiǎn)化的還不僅僅是內(nèi)存管理,還可以簡(jiǎn)化對(duì)資源的管理,例如 fd,鎖,引用計(jì)數(shù)等等。

當(dāng)我們需要在堆上分配內(nèi)存時(shí),我們可以同時(shí)在棧上面分配一個(gè)對(duì)象,讓棧上面的對(duì)象對(duì)堆上面的對(duì)象進(jìn)行封裝,用時(shí)通過在棧對(duì)象的析構(gòu)函數(shù)中釋放堆內(nèi)存的方式,將棧對(duì)象的生命周期和堆內(nèi)存進(jìn)行綁定。

unique_ptr 就是一種很典型的例子。然而 unique_ptr 管理的對(duì)象類型只能是指針,對(duì)于其他的資源,例如 fd,我們可以通過將 fd 封裝成另外一個(gè) FileHandle 對(duì)象的方式管理,也可以采用一些更通用的方式。例如,在我們內(nèi)部的 C++ 基礎(chǔ)庫(kù)中實(shí)現(xiàn)了 Defer 類,想法類似于 Go 中 defer。

void Foo() {

    int fd = open();

    Defer d = [=]() { close(fd); }

    // do something with fd

}

經(jīng)驗(yàn) #5:便于 Debug

在特定的情況下,我們難免還是要手動(dòng)管理堆上的內(nèi)存。然而當(dāng)我們面臨一個(gè)正在發(fā)生內(nèi)存泄露線上程序時(shí),我們應(yīng)該怎么處理呢?

當(dāng)然不是簡(jiǎn)單的『重啟大法好』,畢竟重啟后還是可能會(huì)產(chǎn)生泄露,而且最寶貴的現(xiàn)場(chǎng)也被破壞了。最佳的方式,還是利用現(xiàn)場(chǎng)進(jìn)行 Debug,這就要求程序具有便于 Debug 的能力。

這里不得不提到一個(gè)經(jīng)典而強(qiáng)大的工具 gperftools。gperftools 是 google 開源的一個(gè)工具集,包含了 tcmalloc,heap profiler,heap checker,cpu profiler 等等。gperftools 的作者之一,就是大名鼎鼎的 Sanjay Ghemawat,沒錯(cuò),就是與 Jeff Dean 齊名,并和他一起寫 MapReduce 的那個(gè) Sanjay。

gperftools 的一些經(jīng)典用法,我們就不在這里進(jìn)行介紹了,大家可以自行查看文檔。而使用 gperftools 可以在不重啟程序的情況下,進(jìn)行內(nèi)存泄露檢查,這個(gè)恐怕是很少有人了解。

實(shí)際上我們 Release 版本的 C++ 程序可執(zhí)行文件在編譯時(shí)全部都鏈接了 gperftools。在 gperftools 的 heap profiler 中,提供了 HeapProfilerStart 和 HeapProfilerStop 的接口,使得我們可以在運(yùn)行時(shí)啟動(dòng)和停止 heap profiler。同時(shí),我們每個(gè)程序都暴露了 RPC 接口,用于接收控制命令和調(diào)試命令。在調(diào)試命令中,我們就增加了調(diào)用 HeapProfilerStart 和 HeapProfilerStop 的命令。由于鏈接了 tcmalloc,所以 tcmalloc 可以獲取所有內(nèi)存分配和回收的信息。當(dāng) heap profiler 啟動(dòng)后,就會(huì)定期的將程序內(nèi)存分配和回收的行為 dump 到一個(gè)臨時(shí)文件中。

當(dāng)程序運(yùn)行一段時(shí)間后,你將得到一組 heap profile 文件

profile.0001.heap
  profile.0002.heap
  ...
  profile.0100.heap

每個(gè) profile 文件中都包含了一段時(shí)間內(nèi),程序中內(nèi)存分配和回收的記錄。如果想要找到內(nèi)存泄露的線索,可以通過使用

pprof --base=profile.0001.heap /usr/bin/xxx profile.0100.heap --text

來進(jìn)行查看,也可以生成 pdf 文件,會(huì)更直觀一些。

圖片

這樣一來,我們就可以很方便的對(duì)線上程序的內(nèi)存泄露進(jìn)行 Debug 了。

寫在最后

C++ 可謂是最復(fù)雜、最靈活的語(yǔ)言,也最容易給大家?guī)砝_。如果想要用好 C++,團(tuán)隊(duì)必須保持比較成熟的心態(tài),團(tuán)隊(duì)成員必須愿意按照一定的規(guī)則來使用 C++,而不是任性的隨意發(fā)揮。這樣大家才能把更多精力放在業(yè)務(wù)本身,而不是編程語(yǔ)言的特性上。

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 內(nèi)存
    +關(guān)注

    關(guān)注

    8

    文章

    3025

    瀏覽量

    74055
  • 程序
    +關(guān)注

    關(guān)注

    117

    文章

    3787

    瀏覽量

    81049
  • C++
    C++
    +關(guān)注

    關(guān)注

    22

    文章

    2108

    瀏覽量

    73651
  • 線程
    +關(guān)注

    關(guān)注

    0

    文章

    504

    瀏覽量

    19684
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    使用valgrind對(duì)代碼進(jìn)行內(nèi)存泄露檢測(cè)

    代碼可能存在內(nèi)存泄露怎么辦?
    發(fā)表于 08-21 15:30 ?401次閱讀
    使用valgrind對(duì)代碼進(jìn)行<b class='flag-5'>內(nèi)存</b><b class='flag-5'>泄露</b>檢測(cè)

    關(guān)于labview中的內(nèi)存泄露

    。4.最好不要用順序結(jié)構(gòu),特別是層疊式順序結(jié)構(gòu)。NI工程師評(píng)論:內(nèi)存泄露的問題,這個(gè)問題比較普遍,DAQmx任務(wù)涉及到硬件資源,凡是打開了硬件就需要及時(shí)的關(guān)閉硬件 就我個(gè)人習(xí)慣而言,如果有可能在一個(gè)程序中
    發(fā)表于 12-06 16:05

    分析你App的內(nèi)存使用之找到內(nèi)存泄露

    INSTRUMENTS調(diào)試工具的使用(二十九) —— 分析你App的內(nèi)存使用之找到內(nèi)存泄露(四)
    發(fā)表于 05-14 16:02

    內(nèi)存泄露內(nèi)存溢出是什么意思

    面試題目匯總最重要:簡(jiǎn)單又重點(diǎn)突出的自我介紹!1、內(nèi)存泄露內(nèi)存溢出是什么意思2、static的使用3、break 和continue的區(qū)別4、指針函數(shù)和函數(shù)指針的區(qū)別5、數(shù)組和鏈表的區(qū)別
    發(fā)表于 12-20 07:47

    怎樣去解決單片機(jī)使用malloc產(chǎn)生內(nèi)存泄露的問題呢

    為什么單片機(jī)使用malloc會(huì)導(dǎo)致內(nèi)存泄露呢?怎樣去解決單片機(jī)使用malloc產(chǎn)生內(nèi)存泄露的問題呢?
    發(fā)表于 01-27 06:23

    怎么去解決paho mqtt和mymqtt的內(nèi)存泄露問題呢?

    我在使用paho mqtt和mymqtt這兩個(gè)軟件包的時(shí)候,存在內(nèi)存泄露問題。每次mqtt發(fā)送數(shù)據(jù)前后后,用free查看內(nèi)存發(fā)送前內(nèi)存情況:total memory: 441216us
    發(fā)表于 02-01 16:03

    請(qǐng)教一下大神ec200x內(nèi)存泄露是何原因呢?

    ){ ec200x_init(device); 初始化ec200,開啟網(wǎng)絡(luò)功能 //聯(lián)網(wǎng)通信發(fā)送。(經(jīng)過驗(yàn)證,網(wǎng)絡(luò)傳輸這一塊代碼沒有內(nèi)存泄露) ec200x_deinit(device);關(guān)閉ec200,關(guān)閉
    發(fā)表于 05-17 11:25

    全志R128內(nèi)存泄露調(diào)試案例

    內(nèi)存泄露調(diào)試案例 問題背景 硬件:R128 軟件:FreeRTOS + rtplayer_test(Cedarx)+ AudioSystem 問題復(fù)現(xiàn) 復(fù)現(xiàn)步驟: rtplayer_test
    發(fā)表于 12-11 10:57

    記一次調(diào)試python內(nèi)存泄露的問題解決方案分享

    python作為動(dòng)態(tài)類型語(yǔ)言同時(shí)擁有垃圾回收機(jī)怎么會(huì)有內(nèi)存泄露? 其實(shí)也有可能出現(xiàn)內(nèi)存泄露的情況, 有如下幾種。
    的頭像 發(fā)表于 12-18 16:55 ?4525次閱讀
    記一次調(diào)試python<b class='flag-5'>內(nèi)存</b><b class='flag-5'>泄露</b>的問題解決方案分享

    內(nèi)存溢出和內(nèi)存泄露的區(qū)別_內(nèi)存溢出的原因以及解決方法

    內(nèi)存溢出和內(nèi)存泄露的區(qū)別是什么?內(nèi)存溢出怎么解決?內(nèi)存溢出是指程序在申請(qǐng)內(nèi)存時(shí),沒有足夠的
    發(fā)表于 06-01 10:27 ?2917次閱讀

    記錄單片機(jī)使用malloc產(chǎn)生內(nèi)存泄露的問題及解決方法

    項(xiàng)目場(chǎng)景:?jiǎn)纹瑱C(jī)使用malloc產(chǎn)生內(nèi)存泄露的問題問題描述:bug1:創(chuàng)建了一個(gè)結(jié)構(gòu)體指針,通過malloc動(dòng)態(tài)開辟內(nèi)存的方式開辟了一段內(nèi)存空間,然后進(jìn)行寫入數(shù)據(jù)修改數(shù)據(jù)的操作,但是下
    發(fā)表于 12-03 10:21 ?8次下載
    記錄單片機(jī)使用malloc產(chǎn)生<b class='flag-5'>內(nèi)存</b><b class='flag-5'>泄露</b>的問題及解決方法

    【RT-Thread學(xué)習(xí)筆記】用memwatch排除內(nèi)存泄露

    【RT-Thread學(xué)習(xí)筆記】使用memwatch排除內(nèi)存泄露
    的頭像 發(fā)表于 07-30 14:01 ?2340次閱讀
    【RT-Thread學(xué)習(xí)筆記】用memwatch排除<b class='flag-5'>內(nèi)存</b><b class='flag-5'>泄露</b>

    glibc導(dǎo)致的堆外內(nèi)存泄露的排查過程

    本文記錄一次glibc導(dǎo)致的堆外內(nèi)存泄露的排查過程。
    的頭像 發(fā)表于 09-01 09:43 ?722次閱讀
    glibc導(dǎo)致的堆外<b class='flag-5'>內(nèi)存</b><b class='flag-5'>泄露</b>的排查過程

    如何使用valgrind對(duì)代碼進(jìn)行內(nèi)存泄露檢測(cè)

    代碼可能存在 內(nèi)存泄露 怎么辦? 使用 valgrind 可以對(duì)代碼進(jìn)行內(nèi)存泄露檢測(cè)。 valgrind下載安裝 安裝: 1 、tar –jxvf valgrind- 3 . 21 .
    的頭像 發(fā)表于 10-04 14:56 ?864次閱讀
    如何使用valgrind對(duì)代碼進(jìn)行<b class='flag-5'>內(nèi)存</b><b class='flag-5'>泄露</b>檢測(cè)

    mtrace分析內(nèi)存泄露

    一、mtrace分析內(nèi)存泄露 mtrace(memory trace),是 GNU Glibc 自帶的內(nèi)存問題檢測(cè)工具,它可以用來協(xié)助定位內(nèi)存泄露
    的頭像 發(fā)表于 11-13 10:55 ?1306次閱讀
    mtrace分析<b class='flag-5'>內(nèi)存</b><b class='flag-5'>泄露</b>
    主站蜘蛛池模板: 免费一级特黄特色大片| 在线免费视频网站| 中文字幕一区二区三区在线播放 | 91男女视频| 特级生活片| 成人免费看毛片| 1024手机在线观看你懂的| 一级特黄aaa大片在线观看| 狠狠躁夜夜躁人人躁婷婷视频| 天堂在线最新版www中文| 欧美成年性色mmm| 免费看一级毛片| 理论片久久| 亚洲欧美婷婷| 无遮挡很爽很污很黄的网站w| 伊人电影综合网| 二级黄的全免费视频| 婷婷色香五月激情综合2020| 搜索黄色毛片| 午夜性a一级毛片| 久久精品国产99国产精品免费看| 欧美性色欧美a在线观看| 人人玩人人添天天爽| 在线亚洲日产一区二区| 国产精品漂亮美女在线观看| 人人澡人人澡碰人人看软件 | 拍拍拍无挡视频免费全程1000| 天天影视亚洲| 最新版天堂中文在线官网| 国产卡1卡2卡三卡网站免费| 啪啪免费网站| 免费一级大片| 国产福利2021最新在线观看| 色偷偷成人网免费视频男人的天堂 | 男男宿舍高h炒肉bl| 久久精品9| 日韩激情淫片免费看| 手机亚洲第1页| 欧美一级高清黄图片| 亚州黄色网址| 天堂在线链接|