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

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

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

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

經(jīng)常遇到的導致內(nèi)存泄漏的原因

Linux愛好者 ? 來源:Linux愛好者 ? 作者:Linux愛好者 ? 2022-06-06 14:12 ? 次閱讀

作為C/C++開發(fā)人員,內(nèi)存泄漏是最容易遇到的問題之一,這是由C/C++語言的特性引起的。C/C++語言與其他語言不同,需要開發(fā)者去申請和釋放內(nèi)存,即需要開發(fā)者去管理內(nèi)存,如果內(nèi)存使用不當,就容易造成段錯誤(segment fault)或者內(nèi)存泄漏(memory leak)。

今天,借助此文,分析下項目中經(jīng)常遇到的導致內(nèi)存泄漏的原因,以及如何避免和定位內(nèi)存泄漏。

主要內(nèi)容如下:

ebc0584e-e54c-11ec-ba43-dac502259ad0.png

背景

C/C++語言中,內(nèi)存的分配與回收都是由開發(fā)人員在編寫代碼時主動完成的,好處是內(nèi)存管理的開銷較小,程序擁有更高的執(zhí)行效率;弊端是依賴于開發(fā)者的水平,隨著代碼規(guī)模的擴大,極容易遺漏釋放內(nèi)存的步驟,或者一些不規(guī)范的編程可能會使程序具有安全隱患。如果對內(nèi)存管理不當,可能導致程序中存在內(nèi)存缺陷,甚至會在運行時產(chǎn)生內(nèi)存故障錯誤。

內(nèi)存泄漏是各類缺陷中十分棘手的一種,對系統(tǒng)的穩(wěn)定運行威脅較大。當動態(tài)分配的內(nèi)存在程序結(jié)束之前沒有被回收時,則發(fā)生了內(nèi)存泄漏。由于系統(tǒng)軟件,如操作系統(tǒng)、編譯器、開發(fā)環(huán)境等都是由C/C++語言實現(xiàn)的,不可避免地存在內(nèi)存泄漏缺陷,特別是一些在服務器上長期運行的軟件,若存在內(nèi)存泄漏則會造成嚴重后果,例如性能下降、程序終止、系統(tǒng)崩潰、無法提供服務等。

所以,本文從原因避免以及定位幾個方面去深入講解,希望能給大家?guī)韼椭?/p>

概念

內(nèi)存泄漏(Memory Leak)是指程序中己動態(tài)分配的堆內(nèi)存由于某種原因程序未釋放或無法釋放,造成系統(tǒng)內(nèi)存的浪費,導致程序運行速度減慢甚至系統(tǒng)崩潰等嚴重后果。

當我們在程序中對原始指針(raw pointer)使用new操作符或者free函數(shù)的時候,實際上是在堆上為其分配內(nèi)存,這個內(nèi)存指的是RAM,而不是硬盤等永久存儲。持續(xù)申請而不釋放(或者少量釋放)內(nèi)存的應用程序,最終因內(nèi)存耗盡導致OOM(out of memory)。

ebf8d7aa-e54c-11ec-ba43-dac502259ad0.png

方便大家理解內(nèi)存泄漏的危害,舉個簡單的例子。有一個賓館,共有100間房間,顧客每次都是在前臺進行登記,然后拿到房間鑰匙。如果有些顧客不需要該房間了,既不去前臺處登記退房,也不歸還鑰匙,久而久之,前臺處可用房間越來越少,收入也越來越少,瀕臨倒閉。

當程序申請了內(nèi)存,而不進行歸還,久而久之,可用內(nèi)存越來越少,OS就會進行自我保護,殺掉該進程,這就是我們常說的OOM(out of memory)。

分類

內(nèi)存泄漏分為以下兩類:

  • 堆內(nèi)存泄漏:我們經(jīng)常說的內(nèi)存泄漏就是堆內(nèi)存泄漏,在堆上申請了資源,在結(jié)束使用的時候,沒有釋放歸還給OS,從而導致該塊內(nèi)存永遠不會被再次使用
  • 資源泄漏:通常指的是系統(tǒng)資源,比如socket,文件描述符等,因為這些在系統(tǒng)中都是有限制的,如果創(chuàng)建了而不歸還,久而久之,就會耗盡資源,導致其他程序不可用

本文主要分析堆內(nèi)存泄漏,所以后面的內(nèi)存泄漏均指的是堆內(nèi)存泄漏。

根源

內(nèi)存泄漏,主要指的是在堆(heap)上申請的動態(tài)內(nèi)存泄漏,或者說是指針指向的內(nèi)存塊忘了被釋放,導致該塊內(nèi)存不能再被申請重新使用。

之前在知乎上看了一句話,指針是C的精髓,也是初學者的一個坎。換句話說,內(nèi)存管理是C的精髓,C/C++可以直接跟OS打交道,從性能角度出發(fā),開發(fā)者可以根據(jù)自己的實際使用場景靈活進行內(nèi)存分配和釋放。

雖然在C++中自C++11引入了smart pointer,雖然很大程度上能夠避免使用裸指針,但仍然不能完全避免,最重要的一個原因是你不能保證組內(nèi)其他人不適用指針,更不能保證合作部門不使用指針。

那么為什么C/C++中會存在指針呢?

這就得從進程的內(nèi)存布局說起。

進程內(nèi)存布局

ec2bffcc-e54c-11ec-ba43-dac502259ad0.png

上圖為32位進程的內(nèi)存布局,從上圖中主要包含以下幾個塊:

  • 內(nèi)核空間:供內(nèi)核使用,存放的是內(nèi)核代碼和數(shù)據(jù)
  • stack:這就是我們經(jīng)常所說的棧,用來存儲自動變量(automatic variable)
  • mmap:也成為內(nèi)存映射,用來在進程虛擬內(nèi)存地址空間中分配地址空間,創(chuàng)建和物理內(nèi)存的映射關系
  • heap:就是我們常說的堆,動態(tài)內(nèi)存的分配都是在堆上
  • bss:包含所有未初始化的全局和靜態(tài)變量,此段中的所有變量都由0或者空指針初始化,程序加載器在加載程序時為BSS段分配內(nèi)存
  • ds:初始化的數(shù)據(jù)塊
    • 包含顯式初始化的全局變量和靜態(tài)變量
    • 此段的大小由程序源代碼中值的大小決定,在運行時不會更改
    • 它具有讀寫權(quán)限,因此可以在運行時更改此段的變量值
    • 該段可進一步分為初始化只讀區(qū)和初始化讀寫區(qū)
  • text:也稱為文本段
    • 該段包含已編譯程序的二進制文件。
    • 該段是一個只讀段,用于防止程序被意外修改
    • 該段是可共享的,因此對于文本編輯器等頻繁執(zhí)行的程序,內(nèi)存中只需要一個副本

由于本文主要講內(nèi)存分配相關,所以下面的內(nèi)容僅涉及到棧(stack)和堆(heap)。

ec7b00ae-e54c-11ec-ba43-dac502259ad0.png

棧一塊連續(xù)的內(nèi)存塊,棧上的內(nèi)存分配就是在這一塊連續(xù)內(nèi)存塊上進行操作的。編譯器在編譯的時候,就已經(jīng)知道要分配的內(nèi)存大小,當調(diào)用函數(shù)時候,其內(nèi)部的遍歷都會在棧上分配內(nèi)存;當結(jié)束函數(shù)調(diào)用時候,內(nèi)部變量就會被釋放,進而將內(nèi)存歸還給棧。

classObject{
public:
Object()=default;
//....
};

voidfun(){
Objectobj;

//dosth
}

在上述代碼中,obj就是在棧上進行分配,當出了fun作用域的時候,會自動調(diào)用Object的析構(gòu)函數(shù)對其進行釋放。

前面有提到,局部變量會在作用域(如函數(shù)作用域、塊作用域等)結(jié)束后析構(gòu)、釋放內(nèi)存。因為分配和釋放的次序是剛好完全相反的,所以可用到堆棧先進后出(first-in-last-out, FILO)的特性,而 C++ 語言的實現(xiàn)一般也會使用到調(diào)用堆棧(call stack)來分配局部變量(但非標準的要求)。

因為棧上內(nèi)存分配和釋放,是一個進棧和出棧的過程(對于編譯器只是一個指令),所以相比于堆上的內(nèi)存分配,棧要快的多。

雖然棧的訪問速度要快于堆,每個線程都有一個自己的棧,棧上的對象是不能跨線程訪問的,這就決定了??臻g大小是有限制的,如果??臻g過大,那么在大型程序中幾十乃至上百個線程,光棧空間就消耗了RAM,這就導致heap的可用空間變小,影響程序正常運行。

設置

Linux系統(tǒng)上,可用通過如下命令來查看棧大小:

ulimit-s
10240

在筆者的機器上,執(zhí)行上述命令輸出結(jié)果是10240(KB)即10m,可以通過shell命令修改棧大小。

ulimit-s102400

通過如上命令,可以將??臻g臨時修改為100m,可以通過下面的命令:

/etc/security/limits.conf

分配方式

靜態(tài)分配

靜態(tài)分配由編譯器完成,假如局部變量以及函數(shù)參數(shù)等,都在編譯期就分配好了。

voidfun(){
inta[10];
}

上述代碼中,a占10 * sizeof(int)個字節(jié),在編譯的時候直接計算好了,運行的時候,直接進棧出棧。

動態(tài)分配

可能很多人認為只有堆上才會存在動態(tài)分配,在棧上只可能是靜態(tài)分配。其實,這個觀點是錯的,棧上也支持動態(tài)分配,該動態(tài)分配由alloca()函數(shù)進行分配。棧的動態(tài)分配和堆是不同的,通過alloca()函數(shù)分配的內(nèi)存由編譯器進行釋放,無需手動操作。

特點

  • 分配速度快:分配大小由編譯器在編譯期完成
  • 不會產(chǎn)生內(nèi)存碎片:棧內(nèi)存分配是連續(xù)的,以FILO的方式進棧和出棧
  • 大小受限:棧的大小依賴于操作系統(tǒng)
  • 訪問受限:只能在當前函數(shù)或者作用域內(nèi)進行訪問

堆(heap)是一種內(nèi)存管理方式。內(nèi)存管理對操作系統(tǒng)來說是一件非常復雜的事情,因為首先內(nèi)存容量很大,其次就是內(nèi)存需求在時間和大小塊上沒有規(guī)律(操作系統(tǒng)上運行著幾十甚至幾百個進程,這些進程可能隨時都會申請或者是釋放內(nèi)存,并且申請和釋放的內(nèi)存塊大小是隨意的)。

堆這種內(nèi)存管理方式的特點就是自由(隨時申請、隨時釋放、大小塊隨意)。堆內(nèi)存是操作系統(tǒng)劃歸給堆管理器(操作系統(tǒng)中的一段代碼,屬于操作系統(tǒng)的內(nèi)存管理單元)來管理的,堆管理器提供了對應的接口_sbrk、_mmap等,只是該接口往往由運行時庫(Linux為glibc)進行調(diào)用,即也可以說由運行時庫進行堆內(nèi)存管理,運行時庫提供了malloc/free函數(shù)由開發(fā)人員調(diào)用,進而使用堆內(nèi)存。

分配方式

正如我們所理解的那樣,由于是在運行期進行內(nèi)存分配,分配的大小也在運行期才會知道,所以堆只支持動態(tài)分配,內(nèi)存申請和釋放的行為由開發(fā)者自行操作,這就很容易造成我們說的內(nèi)存泄漏。

特點

  • 變量可以在進程范圍內(nèi)訪問,即進程內(nèi)的所有線程都可以訪問該變量
  • 沒有內(nèi)存大小限制,這個其實是相對的,只是相對于棧大小來說沒有限制,其實最終還是受限于RAM
  • 相對棧來說訪問比較慢
  • 內(nèi)存碎片
  • 由開發(fā)者管理內(nèi)存,即內(nèi)存的申請和釋放都由開發(fā)人員來操作

堆與棧區(qū)別

理解堆和棧的區(qū)別,對我們開發(fā)過程中會非常有用,結(jié)合上面的內(nèi)容,總結(jié)下二者的區(qū)別。

對于棧來講,是由編譯器自動管理,無需我們手工控制;對于堆來說,釋放工作由程序員控制,容易產(chǎn)生memory leak

  • 空間大小不同
    • 一般來講在 32 位系統(tǒng)下,堆內(nèi)存可以達到3G的空間,從這個角度來看堆內(nèi)存幾乎是沒有什么限制的。
    • 對于棧來講,一般都是有一定的空間大小的,一般依賴于操作系統(tǒng)(也可以人工設置)
  • 能否產(chǎn)生碎片不同
    • 對于堆來講,頻繁的內(nèi)存分配和釋放勢必會造成內(nèi)存空間的不連續(xù),從而造成大量的碎片,使程序效率降低。
    • 對于棧來講,內(nèi)存都是連續(xù)的,申請和釋放都是指令移動,類似于數(shù)據(jù)結(jié)構(gòu)中的進棧和出棧
  • 增長方向不同
    • 對于堆來講,生長方向是向上的,也就是向著內(nèi)存地址增加的方向
    • 對于棧來講,它的生長方向是向下的,是向著內(nèi)存地址減小的方向增長
  • 分配方式不同
    • 堆都是動態(tài)分配的,比如我們常見的malloc/new;而棧則有靜態(tài)分配和動態(tài)分配兩種。
    • 靜態(tài)分配是編譯器完成的,比如局部變量的分配,而棧的動態(tài)分配則通過alloca()函數(shù)完成
    • 二者動態(tài)分配是不同的,棧的動態(tài)分配的內(nèi)存由編譯器進行釋放,而堆上的動態(tài)分配的內(nèi)存則必須由開發(fā)人自行釋放
  • 分配效率不同
    • 棧有操作系統(tǒng)分配專門的寄存器存放棧的地址,壓棧出棧都有專門的指令執(zhí)行,這就決定了棧的效率比較高
    • 堆內(nèi)存的申請和釋放專門有運行時庫提供的函數(shù),里面涉及復雜的邏輯,申請和釋放效率低于棧

截止到這里,棧和堆的基本特性以及各自的優(yōu)缺點、使用場景已經(jīng)分析完成,在這里給開發(fā)者一個建議,能使用棧的時候,就盡量使用棧,一方面是因為效率高于堆,另一方面內(nèi)存的申請和釋放由編譯器完成,這樣就避免了很多問題。

擴展

終于到了這一小節(jié),其實,上面講的那么多,都是為這一小節(jié)做鋪墊。

在前面的內(nèi)容中,我們對比了棧和堆,雖然棧效率比較高,且不存在內(nèi)存泄漏、內(nèi)存碎片等,但是由于其本身的局限性(不能多線程、大小受限),所以在很多時候,還是需要在堆上進行內(nèi)存。

我們先看一段代碼:

#include
#include

intmain(){
inta;
int*p;
p=(int*)malloc(sizeof(int));
free(p);

return0;
}

上述代碼很簡單,有兩個變量a和p,類型分別為int和int *,其中,a和p存儲在棧上,p的值為在堆上的某塊地址(在上述代碼中,p的值為0x1c66010),上述代碼布局如下圖所示:

ec966650-e54c-11ec-ba43-dac502259ad0.png

產(chǎn)生方式

以產(chǎn)生的方式來分類,內(nèi)存泄漏可以分為四類:

  • 常發(fā)性內(nèi)存泄漏
  • 偶發(fā)性內(nèi)存泄漏
  • 一次性內(nèi)存泄漏
  • 隱式內(nèi)存泄漏

常發(fā)性內(nèi)存泄漏

產(chǎn)生內(nèi)存泄漏的代碼或者函數(shù)會被多次執(zhí)行到,在每次執(zhí)行的時候,都會產(chǎn)生內(nèi)存泄漏。

偶發(fā)性內(nèi)存泄漏

常發(fā)性內(nèi)存泄漏不同的是,偶發(fā)性內(nèi)存泄漏函數(shù)只在特定的場景下才會被執(zhí)行。

筆者在19年的時候,曾經(jīng)遇到一個這種內(nèi)存泄漏。有一個函數(shù)專門進行價格加密,每次泄漏3個字節(jié),且只有在競價成功的時候,才會調(diào)用此函數(shù)進行價格加密,因此泄漏的非常不明顯。

當時發(fā)現(xiàn)這個問題,是上線后的第二天,幫忙排查線上問題,發(fā)現(xiàn)內(nèi)存較上線前上漲了點(大概幾百兆的樣子),了解glibc內(nèi)存分配原理的都清楚,調(diào)用delete后,內(nèi)存不一定會歸還給OS,但是本著寧可信其有,不可信其無的心態(tài),決定來分析是否真的存在內(nèi)存泄漏。

當時用了個比較傻瓜式的方法,通過top命令,將該進程所占的內(nèi)存輸出到本地文件,大概幾個小時后,將這些數(shù)據(jù)導入Excel中,內(nèi)存占用基本呈一條斜線,所以基本能夠確定代碼存在內(nèi)存泄漏,所以就對新上線的這部分代碼進行重新review,定位到泄漏點,然后修復,重新上線。

一次性內(nèi)存泄漏

這種內(nèi)存泄漏在程序的生命周期內(nèi)只會泄漏一次,或者說造成泄漏的代碼只會被執(zhí)行一次。

有的時候,這種可能不算內(nèi)存泄漏,或者說設計如此。就以筆者現(xiàn)在線上的服務來說,類似于如下這種:

intmain(){
auto*service=newService;
//dosth
service->Run();//服務啟動
service->Loop();//可以理解為一個sleep,目的是使得程序不退出
return0;
}

這種嚴格意義上,并不算內(nèi)存泄漏,因為程序是這么設計的,即使程序異常退出,那么整個服務進程也就退出了,當然,在Loop()后面加個delete更好。

隱式內(nèi)存泄漏

程序在運行過程中不停的分配內(nèi)存,但是直到結(jié)束的時候才釋放內(nèi)存。嚴格的說這里并沒有發(fā)生內(nèi)存泄漏,因為最終程序釋放了所有申請的內(nèi)存。但是對于一個服務器程序,需要運行幾天,幾周甚至幾個月,不及時釋放內(nèi)存也可能導致最終耗盡系統(tǒng)的所有內(nèi)存。所以,我們稱這類內(nèi)存泄漏為隱式內(nèi)存泄漏。

比較常見的隱式內(nèi)存泄漏有以下三種:

  • 內(nèi)存碎片:還記得我們之前的那篇文章深入理解glibc內(nèi)存管理精髓,程序跑了幾天之后,進程就因為OOM導致了退出,就是因為內(nèi)存碎片導致剩下的內(nèi)存不能被重新分配導致
  • 即使我們調(diào)用了free/delete,運行時庫不一定會將內(nèi)存歸還OS,具體深入理解glibc內(nèi)存管理精髓
  • 用過STL的知道,STL內(nèi)部有一個自己的allocator,我們可以當做一個memory poll,當調(diào)用vector.clear()時候,內(nèi)存并不會歸還OS,而是放回allocator,其內(nèi)部根據(jù)一定的策略,在特定的時候?qū)?nèi)存歸還OS,是不是跟glibc原理很像

分類

未釋放

這種是很常見的,比如下面的代碼:

intfun(){
char*pBuffer=malloc(sizeof(char));

/*Dosomework*/
return0;
}

上面代碼是非常常見的內(nèi)存泄漏場景(也可以使用new來進行分配),我們申請了一塊內(nèi)存,但是在fun函數(shù)結(jié)束時候沒有調(diào)用free函數(shù)進行內(nèi)存釋放。

在C++開發(fā)中,還有一種內(nèi)存泄漏,如下:

classObj{
public:
Obj(intsize){
buffer_=newchar;
}
~Obj(){}
private:
char*buffer_;
};

intfun(){
Objectobj;
//dosth
return0;
}

上面這段代碼中,析構(gòu)函數(shù)沒有釋放成員變量buffer_指向的內(nèi)存,所以在編寫析構(gòu)函數(shù)的時候,一定要仔細分析成員變量有沒有申請動態(tài)內(nèi)存,如果有,則需要手動釋放,我們重新編寫了析構(gòu)函數(shù),如下:

~Object(){
deletebuffer_;
}

在C/C++中,對于普通函數(shù),如果申請了堆資源,請跟進代碼的具體場景調(diào)用free/delete進行資源釋放;對于class,如果申請了堆資源,則需要在對應的析構(gòu)函數(shù)中調(diào)用free/delete進行資源釋放。

未匹配

在C++中,我們經(jīng)常使用new操作符來進行內(nèi)存分配,其內(nèi)部主要做了兩件事:

  1. 通過operator new從堆上申請內(nèi)存(glibc下,operator new底層調(diào)用的是malloc)
  2. 調(diào)用構(gòu)造函數(shù)(如果操作對象是一個class的話)

對應的,使用delete操作符來釋放內(nèi)存,其順序正好與new相反:

  1. 調(diào)用對象的析構(gòu)函數(shù)(如果操作對象是一個class的話)
  2. 通過operator delete釋放內(nèi)存
void*operatornew(std::size_tsize){
void*p=malloc(size);
if(p==nullptr){
throw("newfailedtoallocate%zubytes",size);
}
returnp;
}
void*operatornew[](std::size_tsize){
void*p=malloc(size);
if(p==nullptr){
throw("new[]failedtoallocate%zubytes",size);
}
returnp;
}

voidoperatordelete(void*ptr)throw(){
free(ptr);
}
voidoperatordelete[](void*ptr)throw(){
free(ptr);
}

為了加深多這塊的理解,我們舉個例子:

classTest{
public:
Test(){
std::cout<"inTest"<std::endl;
}
//other
~Test(){
std::cout<"in~Test"<std::endl;
}
};

intmain(){
Test*t=newTest;
//dosth
deletet;
return0;
}

在上述main函數(shù)中,我們使用new 操作符創(chuàng)建一個Test類指針

  1. 通過operator new申請內(nèi)存(底層malloc實現(xiàn))
  2. 通過placement new在上述申請的內(nèi)存塊上調(diào)用構(gòu)造函數(shù)
  3. 調(diào)用ptr->~Test()釋放Test對象的成員變量
  4. 調(diào)用operator delete釋放內(nèi)存

上述過程,可以理解為如下:

//new
void*ptr=malloc(sizeof(Test));
t=new(ptr)Test

//delete
ptr->~Test();
free(ptr);

好了,上述內(nèi)容,我們簡單的講解了C++中new和delete操作符的基本實現(xiàn)以及邏輯,那么,我們就簡單總結(jié)下下產(chǎn)生內(nèi)存泄漏的幾種類型。

new 和 free

仍然以上面的Test對象為例,代碼如下:

Test*t=newTest;
free(t)

此處會產(chǎn)生內(nèi)存泄漏,在上面,我們已經(jīng)分析過,new操作符會先通過operator new分配一塊內(nèi)存,然后在該塊內(nèi)存上調(diào)用placement new即調(diào)用Test的構(gòu)造函數(shù)。而在上述代碼中,只是通過free函數(shù)釋放了內(nèi)存,但是沒有調(diào)用Test的析構(gòu)函數(shù)以釋放Test的成員變量,從而引起內(nèi)存泄漏

new[] 和 delete

intmain(){
Test*t=newTest[10];
//dosth
deletet;
return0;
}

在上述代碼中,我們通過new創(chuàng)建了一個Test類型的數(shù)組,然后通delete操作符刪除該數(shù)組,編譯并執(zhí)行,輸出如下:

inTest
inTest
inTest
inTest
inTest
inTest
inTest
inTest
inTest
inTest
in~Test

從上面輸出結(jié)果可以看出,調(diào)用了10次構(gòu)造函數(shù),但是只調(diào)用了一次析構(gòu)函數(shù),所以引起了內(nèi)存泄漏。這是因為調(diào)用delete t釋放了通過operator new[]申請的內(nèi)存,即malloc申請的內(nèi)存塊,且只調(diào)用了t[0]對象的析構(gòu)函數(shù),t[1..9]對象的析構(gòu)函數(shù)并沒有被調(diào)用。

虛析構(gòu)

記得08年面谷歌的時候,有一道題,面試官問,std::string能否被繼承,為什么?

當時沒回答上來,后來過了沒多久,進行面試復盤的時候,偶然看到繼承需要父類析構(gòu)函數(shù)為virtual,才恍然大悟,原來考察點在這塊。

下面我們看下std::string的析構(gòu)函數(shù)定義:

~basic_string(){
_M_rep()->_M_dispose(this->get_allocator());
}

這塊需要特別說明下,std::basic_string是一個模板,而std::string是該模板的一個特化,即std::basic_string。

typedefstd::basic_string<char>string;

現(xiàn)在我們可以給出這個問題的答案:不能,因為std::string的析構(gòu)函數(shù)不為virtual,這樣會引起內(nèi)存泄漏。

仍然以一個例子來進行證明。

classBase{
public:
Base(){
buffer_=newchar[10];
}

~Base(){
std::cout<"inBase::~Base"<std::endl;
delete[]buffer_;
}
private:
char*buffer_;

};

classDerived:publicBase{
public:
Derived(){}

~Derived(){
std::cout<"intDerived::~Derived"<std::endl;
}
};

intmain(){
Base*base=newDerived;
deletebase;
return0;
}

上面代碼輸出如下:

inBase::~Base

可見,上述代碼并沒有調(diào)用派生類Derived的析構(gòu)函數(shù),如果派生類中在堆上申請了資源,那么就會產(chǎn)生內(nèi)存泄漏。

為了避免因為繼承導致的內(nèi)存泄漏,我們需要將父類的析構(gòu)函數(shù)聲明為virtual,代碼如下(只列了部分修改代碼,其他不變):

~Base(){
std::cout<"inBase::~Base"<std::endl;
delete[]buffer_;
}

然后重新執(zhí)行代碼,輸出結(jié)果如下:

intDerived::~Derived
inBase::~Base

借助此文,我們再次總結(jié)下存在繼承情況下,構(gòu)造函數(shù)和析構(gòu)函數(shù)的調(diào)用順序。

派生類對象在創(chuàng)建時構(gòu)造函數(shù)調(diào)用順序:

  1. 調(diào)用父類的構(gòu)造函數(shù)
  2. 調(diào)用父類成員變量的構(gòu)造函數(shù)
  3. 調(diào)用派生類本身的構(gòu)造函數(shù)

派生類對象在析構(gòu)時的析構(gòu)函數(shù)調(diào)用順序:

  1. 執(zhí)行派生類自身的析構(gòu)函數(shù)
  2. 執(zhí)行派生類成員變量的析構(gòu)函數(shù)
  3. 執(zhí)行父類的析構(gòu)函數(shù)

為了避免存在繼承關系時候的內(nèi)存泄漏,請遵守一條規(guī)則:無論派生類有沒有申請堆上的資源,請將父類的析構(gòu)函數(shù)聲明為virtual

循環(huán)引用

在C++開發(fā)中,為了盡可能的避免內(nèi)存泄漏,自C++11起引入了smart pointer,常見的有shared_ptr、weak_ptr以及unique_ptr等(auto_ptr已經(jīng)被廢棄),其中weak_ptr是為了解決循環(huán)引用而存在,其往往與shared_ptr結(jié)合使用。

下面,我們看一段代碼:

classController{
public:
Controller()=default;

~Controller(){
std::cout<"in~Controller"<std::endl;
}

classSubController{
public:
SubController()=default;

~SubController(){
std::cout<"in~SubController"<std::endl;
}

std::shared_ptrcontroller_;
};

std::shared_ptrsub_controller_;
};

intmain(){
autocontroller=std::make_shared();
autosub_controller=std::make_shared();

controller->sub_controller_=sub_controller;
sub_controller->controller_=controller;
return0;
}

編譯并執(zhí)行上述代碼,發(fā)現(xiàn)并沒有調(diào)用Controller和SubController的析構(gòu)函數(shù),我們嘗試著打印下引用計數(shù),代碼如下:

intmain(){
autocontroller=std::make_shared();
autosub_controller=std::make_shared();

controller->sub_controller_=sub_controller;
sub_controller->controller_=controller;

std::cout<"controlleruse_count:"<std::endl;
std::cout<"sub_controlleruse_count:"<std::endl;
return0;
}

編譯并執(zhí)行之后,輸出如下:

controlleruse_count:2
sub_controlleruse_count:2

通過上面輸出可以發(fā)現(xiàn),因為引用計數(shù)都是2,所以在main函數(shù)結(jié)束的時候,不會調(diào)用controller和sub_controller的析構(gòu)函數(shù),所以就出現(xiàn)了內(nèi)存泄漏。

上面產(chǎn)生內(nèi)存泄漏的原因,就是我們常說的循環(huán)引用。

ecb55b96-e54c-11ec-ba43-dac502259ad0.png

為了解決std::shared_ptr循環(huán)引用導致的內(nèi)存泄漏,我們可以使用std::weak_ptr來單面去除上圖中的循環(huán)。

classController{
public:
Controller()=default;

~Controller(){
std::cout<"in~Controller"<std::endl;
}

classSubController{
public:
SubController()=default;

~SubController(){
std::cout<"in~SubController"<std::endl;
}

std::weak_ptrcontroller_;
};

std::shared_ptrsub_controller_;
};

在上述代碼中,我們將SubController類中controller_的類型從std::shared_ptr變成std::weak_ptr,重新編譯執(zhí)行,結(jié)果如下:

controlleruse_count:1
sub_controlleruse_count:2
in~Controller
in~SubController

從上面結(jié)果可以看出,controller和sub_controller均以釋放,所以循環(huán)引用引起的內(nèi)存泄漏問題,也得以解決。

ecdb96d0-e54c-11ec-ba43-dac502259ad0.png

可能有人會問,使用std::shared_ptr可以直接訪問對應的成員函數(shù),如果是std::weak_ptr的話,怎么訪問呢?我們可以使用下面的方式:

std::shared_ptrcontroller=controller_.lock();

即在子類SubController中,如果要使用controller調(diào)用其對應的函數(shù),就可以使用上面的方式。

避免

避免在堆上分配

眾所周知,大部分的內(nèi)存泄漏都是因為在堆上分配引起的,如果我們不在堆上進行分配,就不會存在內(nèi)存泄漏了(這不廢話嘛),我們可以根據(jù)具體的使用場景,如果對象可以在棧上進行分配,就在棧上進行分配,一方面棧的效率遠高于堆,另一方面,還能避免內(nèi)存泄漏,我們何樂而不為呢。

手動釋放

  • 對于malloc函數(shù)分配的內(nèi)存,在結(jié)束使用的時候,使用free函數(shù)進行釋放
  • 對于new操作符創(chuàng)建的對象,切記使用delete來進行釋放
  • 對于new []創(chuàng)建的對象,使用delete[]來進行釋放(使用free或者delete均會造成內(nèi)存泄漏)

避免使用裸指針

盡可能避免使用裸指針,除非所調(diào)用的lib庫或者合作部門的接口是裸指針。

intfun(int*ptr){//fun是一個接口或lib函數(shù)
//dosth

return0;
}

intmain(){}
inta=1000;
int*ptr=&a;
//...
fun(ptr);

return0;
}

在上面的fun函數(shù)中,有一個參數(shù)ptr,為int *,我們需要根據(jù)上下文來分析這個指針是否需要釋放,這是一種很不好的設計

使用STL中或者自己實現(xiàn)對象

在C++中,提供了相對完善且可靠的STL供我們使用,所以能用STL的盡可能的避免使用C中的編程方式,比如:

  • 使用std::string 替代char *, string類自己會進行內(nèi)存管理,而且優(yōu)化的相當不錯
  • 使用std::vector或者std::array來替代傳統(tǒng)的數(shù)組
  • 其它適合使用場景的對象

智能指針

自C++11開始,STL中引入了智能指針(smart pointer)來動態(tài)管理資源,針對使用場景的不同,提供了以下三種智能指針。

unique_ptr

unique_ptr是限制最嚴格的一種智能指針,用來替代之前的auto_ptr,獨享被管理對象指針所有權(quán)。當unique_ptr對象被銷毀時,會在其析構(gòu)函數(shù)內(nèi)刪除關聯(lián)的原始指針。

unique_ptr對象分為以下兩類:

  • unique_ptr該類型的對象關聯(lián)了單個Type類型的指針

    std::unique_ptrp1(newType);//c++11
    autop1=std::make_unique();//c++14
    
  • unique_ptr 該類型的對象關聯(lián)了多個Type類型指針,即一個對象數(shù)組

    std::unique_ptrp2(newType[n]());//c++11
    autop2=std::make_unique(n);//c++14
    
  • 不可用被復制

    unique_ptr<int>a(newint(0));
    unique_ptr<int>b=a;//編譯錯誤
    unique_ptr<int>b=std::move(a);//可以通過move語義進行所有權(quán)轉(zhuǎn)移
    

根據(jù)使用場景,可以使用std::unique_ptr來避免內(nèi)存泄漏,如下:

voidfun(){
unique_ptr<int>a(newint(0));
//usea
}

在上述fun函數(shù)結(jié)束的時候,會自動調(diào)用a的析構(gòu)函數(shù),從而釋放其關聯(lián)的指針。

shared_ptr

與unique_ptr不同的是,unique_ptr是獨占管理權(quán),而shared_ptr則是共享管理權(quán),即多個shared_ptr可以共用同一塊關聯(lián)對象,其內(nèi)部采用的是引用計數(shù),在拷貝的時候,引用計數(shù)+1,而在某個對象退出作用域或者釋放的時候,引用計數(shù)-1,當引用計數(shù)為0的時候,會自動釋放其管理的對象。

voidfun(){
std::shared_ptra;//a是一個空對象
{
std::shared_ptrb=std::make_shared();//分配資源
a=b;//此時引用計數(shù)為2
{
std::shared_ptrc=a;//此時引用計數(shù)為3
}//c退出作用域,此時引用計數(shù)為2
}//b退出作用域,此時引用計數(shù)為1
}//a退出作用域,引用計數(shù)為0,釋放對象

weak_ptr

weak_ptr的出現(xiàn),主要是為了解決shared_ptr的循環(huán)引用,其主要是與shared_ptr一起來私用。和shared_ptr不同的地方在于,其并不會擁有資源,也就是說不能訪問對象所提供的成員函數(shù),不過,可以通過weak_ptr.lock()來產(chǎn)生一個擁有訪問權(quán)限的shared_ptr。

std::weak_ptra;
{
std::shared_ptrb=std::make_shared();
a=b
}//b所對應的資源釋放

RAII

RAIIResource Acquisition is Initialization(資源獲取即初始化)的縮寫,是C++語言的一種管理資源,避免泄漏的用法。

利用的就是C++構(gòu)造的對象最終會被銷毀的原則。利用C++對象生命周期的概念來控制程序的資源,比如內(nèi)存,文件句柄,網(wǎng)絡連接等。

RAII的做法是使用一個對象,在其構(gòu)造時獲取對應的資源,在對象生命周期內(nèi)控制對資源的訪問,使之始終保持有效,最后在對象析構(gòu)的時候,釋放構(gòu)造時獲取的資源。

簡單地說,就是把資源的使用限制在對象的生命周期之中,自動釋放。

舉個簡單的例子,通常在多線程編程的時候,都會用到std::mutex,如下代碼:

std::mutexmutex_;

voidfun(){
mutex_.lock();

if(...){
mutex_.unlock();
return;
}

mutex_.unlock()
}

在上述代碼中,如果if分支多的話,每個if分支里面都要釋放鎖,如果一不小心忘記釋放,那么就會造成故障,為了解決這個問題,我們使用RAII技術(shù),代碼如下:

std::mutexmutex_;

voidfun(){
std::lock_guard<std::mutex>guard(mutex_);

if(...){
return;
}
}

在guard出了fun作用域的時候,會自動調(diào)用mutex_.lock()進行釋放,避免了很多不必要的問題。

定位

在發(fā)現(xiàn)程序存在內(nèi)存泄漏后,往往需要定位泄漏點,而定位這一步往往是最困難的,所以經(jīng)常為了定位泄漏點,采取各種各樣的方案,甭管方案優(yōu)雅與否,畢竟管他白貓黑貓,抓住老鼠才是好貓,所以在本節(jié),簡單說下筆者這么多年定位泄漏點的方案,有些比較邪門歪道,您就隨便看看就行。

日志

這種方案的核心思想,就是在每次分配內(nèi)存的時候,打印指針地址,在釋放內(nèi)存的時候,打印內(nèi)存地址,這樣在程序結(jié)束的時候,通過分配和釋放的差,如果分配的條數(shù)大于釋放的條數(shù),那么基本就能確定程序存在內(nèi)存泄漏,然后根據(jù)日志進行詳細分析和定位。

char*fun(){
char*p=(char*)malloc(20);
printf("%s,%d,addressis:%p",__FILE__,__LINE__,p);
//dosth
returnp;
}

intmain(){
fun();

return0;
}

統(tǒng)計

統(tǒng)計方案可以理解為日志方案的一種特殊實現(xiàn),其主要原理是在分配的時候,統(tǒng)計分配次數(shù),在釋放的時候,則是統(tǒng)計釋放的次數(shù),這樣在程序結(jié)束前判斷這倆值是否一致,就能判斷出是否存在內(nèi)存泄漏。

此方法可幫助跟蹤已分配內(nèi)存的狀態(tài)。為了實現(xiàn)這個方案,需要創(chuàng)建三個自定義函數(shù),一個用于內(nèi)存分配,第二個用于內(nèi)存釋放,最后一個用于檢查內(nèi)存泄漏。代碼如下:

staticunsignedintallocated=0;
staticunsignedintdeallocated=0;
void*Memory_Allocate(size_tsize)
{
void*ptr=NULL;
ptr=malloc(size);
if(NULL!=ptr){
++allocated;
}else{
//Logerror
}
returnptr;
}
voidMemory_Deallocate(void*ptr){
if(pvHandle!=NULL){
free(ptr);
++deallocated;
}
}
intCheck_Memory_Leak(void){
intret=0;
if(allocated!=deallocated){
//Logerror
ret=MEMORY_LEAK;
}else{
ret=OK;
}
returnret;
}

工具

在Linux上比較常用的內(nèi)存泄漏檢測工具是valgrind,所以咱們就以valgrind為工具,進行檢測。

我們首先看一段代碼:

#include

voidfunc(void){
char*buff=(char*)malloc(10);
}

intmain(void){
func();//產(chǎn)生內(nèi)存泄漏
return0;
}
  • 通過gcc -g leak.c -o leak命令進行編譯
  • 執(zhí)行valgrind --leak-check=full ./leak

在上述的命令執(zhí)行后,會輸出如下:

==9652==Memcheck,amemoryerrordetector
==9652==Copyright(C)2002-2017,andGNUGPL'd,byJulianSewardetal.
==9652==UsingValgrind-3.15.0andLibVEX;rerunwith-hforcopyrightinfo
==9652==Command:./leak
==9652==
==9652==
==9652==HEAPSUMMARY:
==9652==inuseatexit:10bytesin1blocks
==9652==totalheapusage:1allocs,0frees,10bytesallocated
==9652==
==9652==10bytesin1blocksaredefinitelylostinlossrecord1of1
==9652==at0x4C29F73:malloc(vg_replace_malloc.c:309)
==9652==by0x40052E:func(leak.c:4)
==9652==by0x40053D:main(leak.c:8)
==9652==
==9652==LEAKSUMMARY:
==9652==definitelylost:10bytesin1blocks
==9652==indirectlylost:0bytesin0blocks
==9652==possiblylost:0bytesin0blocks
==9652==stillreachable:0bytesin0blocks
==9652==suppressed:0bytesin0blocks
==9652==
==9652==Forlistsofdetectedandsuppressederrors,rerunwith:-s
==9652==ERRORSUMMARY:1errorsfrom1contexts(suppressed:0from0)

valgrind的檢測信息將內(nèi)存泄漏分為如下幾類:

  • definitely lost:確定產(chǎn)生內(nèi)存泄漏
  • indirectly lost:間接產(chǎn)生內(nèi)存泄漏
  • possibly lost:可能存在內(nèi)存泄漏
  • still reachable:即使在程序結(jié)束時候,仍然有指針在指向該塊內(nèi)存,常見于全局變量

主要上面輸出的下面幾句:

==9652==by0x40052E:func(leak.c:4)
==9652==by0x40053D:main(leak.c:8)

提示在main函數(shù)(leak.c的第8行)fun函數(shù)(leak.c的第四行)產(chǎn)生了內(nèi)存泄漏,通過分析代碼,原因定位,問題解決。

valgrind不僅可以檢測內(nèi)存泄漏,還有其他很強大的功能,由于本文以內(nèi)存泄漏為主,所以其他的功能就不在此贅述了,有興趣的可以通過valgrind --help來進行查看

?

對于Windows下的內(nèi)存泄漏檢測工具,筆者推薦一款輕量級功能卻非常強大的工具UMDH,筆者在十二年前,曾經(jīng)在某外企負責內(nèi)存泄漏,代碼量幾百萬行,光編譯就需要兩個小時,嘗試了各種工具(免費的和收費的),最終發(fā)現(xiàn)了UMDH,如果你在Windows上進行開發(fā),強烈推薦。

?

經(jīng)驗之談

在C/C++開發(fā)過程中,內(nèi)存泄漏是一個非常常見的問題,其影響相對來說遠低于coredump等,所以遇到內(nèi)存泄漏的時候,不用過于著急,大不了重啟嘛。

在開發(fā)過程中遵守下面的規(guī)則,基本能90+%避免內(nèi)存泄漏:

  • 良好的編程習慣,只有有malloc/new,就得有free/delete
  • 盡可能的使用智能指針,智能指針就是為了解決內(nèi)存泄漏而產(chǎn)生
  • 使用log進行記錄
  • 也是最重要的一點,誰申請,誰釋放

對于malloc分配內(nèi)存,分配失敗的時候返回值為NULL,此時程序可以直接退出了,而對于new進行內(nèi)存分配,其分配失敗的時候,是拋出std::bad_alloc,所以為了第一時間發(fā)現(xiàn)問題,不要對new異常進行catch,畢竟內(nèi)存都分配失敗了,程序也沒有運行的必要了。

如果我們上線后,發(fā)現(xiàn)程序存在內(nèi)存泄漏,如果不嚴重的話,可以先暫時不管線上,同時進行排查定位;如果線上泄漏比較嚴重,那么第一時間根據(jù)實際情況來決定是否回滾。在定位問題點的時候,可以采用縮小范圍法,著重分析這次新增的代碼,這樣能夠有效縮短問題解決的時間。

結(jié)語

C/C++之所以復雜、效率高,是因為其靈活性,可用直接訪問操作系統(tǒng)API,而正因為其靈活性,就很容易出問題,團隊成員必須愿意按照一定的規(guī)則來進行開發(fā),有完整的review機制,將問題暴露在上線之前。

這樣才可以把經(jīng)歷放在業(yè)務本身,而不是查找這些問題上,有時候往往一個小問題就能消耗很久的時間去定位解決,所以,一定要有一個良好的開發(fā)習慣。

審核編輯 :李倩


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

    關注

    8

    文章

    3071

    瀏覽量

    74406
  • C++
    C++
    +關注

    關注

    22

    文章

    2114

    瀏覽量

    73922

原文標題:內(nèi)存泄漏-原因、避免以及定位

文章出處:【微信號:LinuxHub,微信公眾號:Linux愛好者】歡迎添加關注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關推薦

    快問快答: 工廠閥門泄漏故障了怎么檢出?檢測方法全攻略來了!

    工廠閥門是工業(yè)生產(chǎn)中不可或缺的關鍵部件,用于控制流體(氣體或液體)的流動。然而,由于長期使用、操作不當或維護不及時等原因,閥門經(jīng)常會出現(xiàn)泄漏故障。閥門泄漏不僅會
    的頭像 發(fā)表于 02-12 11:13 ?131次閱讀
    快問快答: 工廠閥門<b class='flag-5'>泄漏</b>故障了怎么檢出?檢測方法全攻略來了!

    使用DevEco Studio高效解決鴻蒙原生應用內(nèi)存問題

    在鴻蒙原生應用開發(fā)過程中,可能由于種種原因導致應用內(nèi)存未被正常地使用或者歸還至操作系統(tǒng),從而引發(fā)內(nèi)存異常占用、內(nèi)存
    的頭像 發(fā)表于 01-16 14:44 ?229次閱讀

    如何使用DevEco Studio性能調(diào)優(yōu)工具Profiler定位應用內(nèi)存問題

    鴻蒙應用開發(fā)過程中,可能由于種種原因導致應用內(nèi)存未被正的使用或者歸還至操作系統(tǒng),從而引發(fā)內(nèi)存異常占用、內(nèi)存
    的頭像 發(fā)表于 01-16 14:40 ?697次閱讀
    如何使用DevEco Studio性能調(diào)優(yōu)工具Profiler定位應用<b class='flag-5'>內(nèi)存</b>問題

    使用ADS8509時,遇到上電后首次轉(zhuǎn)換結(jié)果不正確的情況,是什么原因導致的?

    在使用ADS8509時,遇到上電后首次轉(zhuǎn)換結(jié)果不正確的情況,但從第二次開始轉(zhuǎn)換結(jié)果就是正確的了,請問下這是什么原因導致的。
    發(fā)表于 12-18 06:14

    虛擬內(nèi)存溢出該怎么處理 虛擬內(nèi)存在服務器中的應用

    、虛擬內(nèi)存溢出的原因 內(nèi)存泄漏 :程序中未正確釋放的內(nèi)存導致
    的頭像 發(fā)表于 12-04 09:49 ?312次閱讀

    MOS管泄漏電流的類型和產(chǎn)生原因

    MOS管(金屬氧化物半導體場效應晶體管)的泄漏電流是指在MOS管關斷狀態(tài)下,從源極或漏極到襯底之間仍然存在的微弱電流。這些泄漏電流可能對電路的性能和穩(wěn)定性產(chǎn)生不利影響,因此需要深入了解其類型和產(chǎn)生原因
    的頭像 發(fā)表于 10-10 15:11 ?2766次閱讀

    內(nèi)存與主板接觸不良,怎么解決

    內(nèi)存與主板接觸不良是計算機常見的故障之一,可能導致計算機無法啟動、頻繁死機、藍屏等問題。 一、內(nèi)存與主板接觸不良的原因 內(nèi)存條質(zhì)量問題
    的頭像 發(fā)表于 09-02 14:35 ?2515次閱讀

    如何檢測內(nèi)存泄漏

    檢測內(nèi)存泄漏是軟件開發(fā)過程中一項至關重要的任務,它有助于識別和解決那些導致程序占用過多內(nèi)存資源,從而影響程序性能甚至導致程序崩潰的問題。以下
    的頭像 發(fā)表于 07-30 11:50 ?2335次閱讀

    Air780E/Air780EP/Air780EQ/Air201模塊遇到內(nèi)存死機如何分析

    平臺模塊出現(xiàn)死機問題分析trace32工具下載EPAT抓取底層日志從Ramdump里分析內(nèi)存泄漏問題對于遇到內(nèi)存不足死機的問題,可以從ramdump里找出哪些函數(shù)在
    的頭像 發(fā)表于 07-19 16:07 ?615次閱讀
    Air780E/Air780EP/Air780EQ/Air201模塊<b class='flag-5'>遇到</b><b class='flag-5'>內(nèi)存</b>死機如何分析

    NONOS 1.5.3/1.5.4 SSL內(nèi)存泄漏原因?

    我已經(jīng)通過隨附的代碼驗證了當發(fā)生 SSL 握手錯誤時,會生成內(nèi)存泄漏 此外,espconn_reconnect_callback不稱為信令ESPCONN_HANDSHAKE - TCP SSL 握手
    發(fā)表于 07-18 07:24

    使用system_show_malloc()檢查內(nèi)存泄漏遇到異常怎么解決?

    我想使用system_show_malloc()檢查內(nèi)存泄漏,但是當我調(diào)用該函數(shù)時,我得到了致命的異常: 致命異常 28 (LoadProhibitedCause): epc1
    發(fā)表于 07-10 06:32

    導致NMEA2000插頭針座變形的原因

    德索工程師說道在NMEA2000插頭針座的使用過程中,我們可能會遇到變形的問題。這種問題不僅會影響設備的正常使用,還可能對設備造成損害。那么,是什么原因導致了NMEA2000插頭針座的變形呢?
    的頭像 發(fā)表于 07-01 16:56 ?310次閱讀
    <b class='flag-5'>導致</b>NMEA2000插頭針座變形的<b class='flag-5'>原因</b>

    C語言內(nèi)存泄漏問題原理

    內(nèi)存泄漏問題只有在使用堆內(nèi)存的時候才會出現(xiàn),棧內(nèi)存不存在內(nèi)存泄漏問題,因為棧
    發(fā)表于 03-19 11:38 ?585次閱讀
    C語言<b class='flag-5'>內(nèi)存</b><b class='flag-5'>泄漏</b>問題原理

    【鴻蒙】webview內(nèi)存泄漏問題的分析報告

    內(nèi)存會隨著使用而不斷增大,最終導致瀏覽器 APP 因內(nèi)存泄漏而崩潰。 3 問題原因 3.1 正常機制 在任意版本上使用瀏覽器 APP,可以
    的頭像 發(fā)表于 03-02 15:12 ?2245次閱讀

    什么是內(nèi)存泄漏?android中導致內(nèi)存泄漏的主要幾個點

    當一個對象已經(jīng)不需要在使用了,本應該被回收,而另一個正在使用的對象持有它的引用,導致對象不能被回收。
    的頭像 發(fā)表于 02-20 10:17 ?679次閱讀
    什么是<b class='flag-5'>內(nèi)存</b><b class='flag-5'>泄漏</b>?android中<b class='flag-5'>導致</b><b class='flag-5'>內(nèi)存</b><b class='flag-5'>泄漏</b>的主要幾個點
    主站蜘蛛池模板: 日本在线一区二区 | 日本www黄 | 国产农村妇女毛片精品久久久 | 色综合天天网 | 天天躁天天爽 | 天天爽夜爽免费精品视频 | 一级特级aaa毛片 | 国产农村一级特黄α真人毛片 | 91色爱| 轻点太大了好深好爽h文 | 国产中文字幕一区 | 天天综合天天做天天综合 | 99精品国产第一福利网站 | 欧美婷婷综合 | 精品久久久久久午夜 | 久久精品久久久 | 色3344| 亚洲国产欧美在线成人aaaa | 精品少妇一区二区三区视频 | 欧美日韩国产一区 | 亚洲 欧美 视频 | 色香影院 | 狠狠干伊人网 | 日本亚洲天堂网 | 亚洲最大成人 | 免费在线播放视频 | 日日天天干 | 亚洲乱码一二三四区 | 好大好猛好爽好深视频免费 | 免费看曰批女人爽的视频网址 | 爱婷婷视频在线观看 | 永久免费看毛片 | 一本到卡二卡三卡视频 | 加勒比精品视频 | 老司机亚洲精品影院在线观看 | 手机免费黄色网址 | 手机在线亚洲 | 中文字幕 视频一区 | 黄网站在线观看 | 中文字幕三级久久久久久 | 女人张开双腿让男人桶爽免 |