1. 程式人生 > >記憶體洩漏以及常見的解決方法

記憶體洩漏以及常見的解決方法

  之所以撰寫這篇文章是因為前段時間花費了很大的精力在已經成熟的程式碼上再去處理memory leak問題。寫此的目的是希望我們應該養成良好的編碼習慣,儘可能的避免這樣的問題,因為當你對著一大片的程式碼再去處理此類的問題,此時無疑增加了解決的成本和難度。準確的說屬於補救措施了。
1. 什麼是記憶體洩漏(memory leak)?
 指由於疏忽或錯誤造成程式未能釋放已經不再使用的記憶體的情況。記憶體洩漏並非指記憶體在物理上的消失,而是應用程式分配某段記憶體後,由於設計錯誤,失去了對該段記憶體的控制,因而造成了記憶體的浪費。 

A memory leak is a particular type of unintentional memory consumption by a computer program where the program fails to release memory when no longer needed. This condition is normally the result of a bug in a program that prevents it from freeing up memory that it no longer needs.This term has the potential to be confusing, since memory is not physically lost from the computer. Rather, memory is allocated to a program, and that program subsequently loses the ability to access it due to program logic flaws. 

2. 對於C和C++這種沒有Garbage Collection 的語言來講,我們主要關注兩種型別的記憶體洩漏:

   堆記憶體洩(Heap leak)。對記憶體指的是程式執行中根據需要分配通過malloc,realloc new等從堆中分配的一塊記憶體,再是完成後必須通過呼叫對應的 free或者delete 刪掉。如果程式的設計的錯誤導致這部分記憶體沒有被釋放,那麼此後這塊記憶體將不會被使用,就會產生Heap Leak. 

  系統資源洩露(Resource Leak).主要指程式使用系統分配的資源比如 Bitmap,handle ,SOCKET等沒有使用相應的函式釋放掉,導致系統資源的浪費,嚴重可導致系統效能降低,系統執行不穩定。  
3. 如何解決記憶體洩露?
記憶體洩露的問題其困難在於1.編譯器不能發現這些問題。2.執行時才能捕獲到這些錯誤,這些錯誤沒有明顯的症狀,時隱時現。3.對於手機等終端開發使用者來說,尤為困難。下面從三個方面來解決記憶體洩露:
第一,良好的編碼習慣,儘量在涉及記憶體的程式段,檢測出記憶體洩露。當程式穩定之後,在來檢測記憶體洩露時,無疑增加了排除的困難和複雜度。
使用了記憶體分配的函式,要記得要使用其想用的函式釋放掉,一旦使用完畢。
Heap memory:
malloc\realloc ------  free
new \new[] ----------  delete \delete[]
GlobalAlloc------------GlobalFree 
要特別注意陣列物件的記憶體洩
     MyPointEX *pointArray =new MyPointEX [100];
      其刪除形式為:
     delete []pointArray 
Resource Leak :對於系統資源使用之前要仔細看起使用方法,防止錯誤使用或者忘記釋放掉系統資源。
我們看MSDN上一個建立字型的例子:
 RECT rect;
HBRUSH hBrush;
FONT hFont;
hdc = BeginPaint(hWnd, &ps);
 hFont = reateFont(48,0,0,0,FW_DONTCARE,FALSE,TRUE,FALSE,DEFAULT_CHARSET,OUT_OUTLINE_PRECIS, CLIP_DEFAULT_PRECIS,CLEARTYPE_QUALITY, VARIABLE_PITCH,TEXT("Impact"));
SelectObject(hdc, hFont); 
SetRect(&rect, 100,100,700,200);
SetTextColor(hdc, RGB(255,0,0));

DrawText(hdc, TEXT("Drawing Text with Impact"), -1,&rect, DT_NOCLIP);    

DeleteObject(hFont);  
 EndPaint(hWnd, &ps);
如果使用完成時候忘記釋放字型,就造成了資源洩漏。 
   對於基於引用計數的系統物件尤其要注意,因為只有其引用計數為0時,該物件才能正確被刪除。而其使用過程中有其生成的新的系統資源,使用完畢後,如果沒有及時刪除,都會影響其引用計數。
 IDNS *m_pDns//define a DNS object.
   If(NULL == m_pDns)
   IEnv_CreateInstance (m_pEnv,AEECLSID_DNS,(void **) (&m_pDns))

  }

 If(m_pDns)
{

    Char szbuff[256];

    IDNS_AddQuestions(M_pDns,AEEDNSTYPE_A,ADDDNSCLASS_IN,szbuff);

    IDNS_Start(m_pDns,this);

    const AEEDNSResponse * pDnsResponse = NULL;

   IDNS_GetResponse(pMe->m_pDns, &pDnsResponse);

…………………………………………………………

…………………………………………………………..

………………………………………………………..

}

DNS_Release(pMe->m_pDns);//當程式執行到此時,其返回值不是0,是1,其含義是程式已經產生記憶體洩露了,系統已經有一個由DNS所產生的核心物件沒有釋放,而當這段程式碼多次執行之後,記憶體洩露將不斷增加……..

m_pDns=NULL;

  }

看起來很不直觀,仔細分析就會發現,物件pDnsResponse是從m_pDns產生新的object,所以m_pDns的引用計數會增加,因此在使用完pDnsResponse,應該release 該物件使其引用計數恢復正常。
對於資源,也可使用RAII,RAII(Resource acquisition is initialization)資源獲取即初始化,它是一項很簡單的技術,利用C++物件生命週期的概念來控制程式的資源,例如記憶體,檔案控制代碼,網路連線以及審計追蹤(audit trail)等.RAII的基本技術原理很簡單.若希望保持對某個重要資源的跟蹤,那麼建立一個物件,並將資源的生命週期和物件的生命週期相關聯.如此一來,就可以利用C++複雜老練的物件管理設施來管理資源.(有待完善)
例2: 
Struct ITypeface *pTypeface;
if (pTypeface)
{
IANY_CreateInstance(g_pApplet->m_pIShell,AEECLSID_BTFETypeface,void**)& Typeface);
} 
接下來我們就可以從這個介面上面建立字型,比如
IHFont **pihf=NULL;
   ITypeface_NewFontFromFile(ITypeface,……,&pihf).
   ITypeface_NewFontFrommemory(ITypeface,……..,&pihf)
   ITypeface_NewFontFromClassID(IType,……,&pihf)
   但是要切記,這些字型在使用完成後一定要release掉,否則最後 iTypeface的引用計數就是你最後沒有刪除掉的字型的個數。 
第二,過載  new 和 delete。這也是大家編碼過程中常常使用的方法。
下面給出簡單的sample來說明。
memchecker.h
structMemIns
{
    void * pMem;
    int m_nSize;
    char m_szFileName[256];
    int m_nLine;
    MemIns * pNext;
};
classMemManager
{
public:
    MemManager();
    ~MemManager();
private:
    MemIns *m_pMemInsHead;
    int m_nTotal;
public:
    static MemManager* GetInstance();
    void Append(MemIns *pMemIns);
    void Remove(void *ptr);
    void Dump(); 
};
void *operatornew(size_tsize,constchar*szFile, int nLine);
void operatordelete(void*ptr,constchar*szFile, int nLine);
 void operatordelete(void*ptr);
void*operatornew[] (size_tsize,constchar*szFile,int nLine);
void operatordelete[](void*ptr,constchar*szFile, int nLine);
void operatordelete[](void *ptr);
memechecker.cpp
#include"Memchecher.h"
#include<stdio.h>
#include<malloc.h>
#include<string.h>
MemManager::MemManager()
{
    m_pMemInsHead=NULL;
    m_nTotal=NULL;
}
MemManager::~MemManager()
{
}
voidMemManager::Append(MemIns *pMemIns)
{
    pMemIns->pNext=m_pMemInsHead;
    m_pMemInsHead = pMemIns;
    m_nTotal+= m_pMemInsHead->m_nSize;
}
voidMemManager::Remove(void *ptr)
{
    MemIns * pCur = m_pMemInsHead;
    MemIns * pPrev = NULL;
    while(pCur)
    {
        if(pCur->pMem ==ptr)
        {
           if(pPrev)
            {
               pPrev->pNext =pCur->pNext;
            }
           else
            {
               m_pMemInsHead =pCur->pNext;
            }
           m_nTotal-=pCur->m_nSize;
           free(pCur);
           break;
        }
        pPrev = pCur;
        pCur = pCur->pNext;
    }
}
voidMemManager::Dump()
{
    MemIns * pp = m_pMemInsHead;
    while(pp)
    {
        printf( "File is %s\n", pp->m_szFileName );
        printf( "Size is %d\n", pp->m_nSize );
        printf( "Line is %d\n", pp->m_nLine );
        pp = pp->pNext;
    }
}
voidPutEntry(void *ptr,intsize,constchar*szFile, int nLine)
{
    MemIns * p = (MemIns *)(malloc(sizeof(MemIns)));
    if(p)
    {
        strcpy(p->m_szFileName,szFile);
        p->m_nLine = nLine;
        p->pMem = ptr;
        p->m_nSize = size;
        MemManager::GetInstance()->Append(p);
    }
}
voidRemoveEntry(void *ptr)
{
    MemManager::GetInstance()->Remove(ptr);
}
void *operatornew(size_tsize,constchar*szFile, int nLine)
{
    void * ptr = malloc(size);
    PutEntry(ptr,size,szFile,nLine);
    return ptr;
}
voidoperatordelete(void *ptr)
{
    RemoveEntry(ptr);
    free(ptr);
}
void operatordelete(void*ptr,constchar * file, intline)
{
    RemoveEntry(ptr);
    free(ptr);
}
void*operatornew[] (size_tsize,constchar* szFile,intnLine)
{
    void * ptr = malloc(size);
    PutEntry(ptr,size,szFile,nLine);
    return ptr;
}
void operatordelete[](void *ptr)
{
    RemoveEntry(ptr);
    free(ptr);
}
void operatordelete[](void*ptr,constchar*szFile,intnLine)

 {

    RemoveEntry(ptr);

    free(ptr);
}
#definenewnew(__FILE__,__LINE__)
MemManagerm_memTracer;
MemManager*MemManager::GetInstance()
{
    return &m_memTracer;
void main()

{

    int *plen =newint ;
    *plen=10;
    delete plen;
    char *pstr=newchar[35];
    strcpy(pstr,"hello memory leak");
    m_memTracer.Dump();
    return ;
}
 其主要思路是將分配的記憶體以連結串列的形式自行管理,使用完畢之後從連結串列中刪除,程式結束時可檢查改連結串列,其中記錄了記憶體洩露的檔案,所在檔案的行數以及洩露的大小哦。
第三,Boost 中的smart pointer(待完善,結合大家的建議)
第四,一些常見的工具外掛,詳見我的Blog中相關文章。
4. 由記憶體洩露引出記憶體溢位話題:
所謂記憶體溢位就是你要求分配的記憶體超出了系統能給你的,系統不能滿足需求,於是會產生記憶體溢位的問題。
常見的溢位主要有:
記憶體分配未成功,卻使用了它。
常用解決辦法是,在使用記憶體之前檢查指標是否為NULL。如果指標p 是函式的引數,那麼在函式的入口處用assert(p!=NULL)進行檢查。如果是用malloc 或new 來申請記憶體,應該用if(p==NULL)或if(p!=NULL)進行防錯處理。
記憶體分配雖然成功,但是尚未初始化就引用它。
記憶體分配成功並且已經初始化,但操作越過了記憶體的邊界。
例如在使用陣列時經常發生下標“多1”或者“少1”的操作。特別是在for 迴圈語句中,迴圈次數很容易搞錯,導致陣列操作越界。
使用free 或delete 釋放了記憶體後,沒有將指標設定為NULL。導致產生“野指標”。
程式中的物件呼叫關係過於複雜,實在難以搞清楚某個物件究竟是否已經釋放了記憶體,此時應該重新設計資料結構,從根本上解決物件管理的混亂局面。(這點可是深有感受,呵呵)

不要忘記為陣列和動態記憶體賦初值。防止將未被初始化的記憶體作為右值使用。

相關推薦

記憶體洩漏以及常見解決方法

  之所以撰寫這篇文章是因為前段時間花費了很大的精力在已經成熟的程式碼上再去處理memory leak問題。寫此的目的是希望我們應該養成良好的編碼習慣,儘可能的避免這樣的問題,因為當你對著一大片的程式碼再去處理此類的問題,此時無疑增加了解決的成本和難度。準確的說屬於補救措

Handler記憶體洩漏分析與解決方法

最近整理完Android中訊息機制的知識後,想到Handler記憶體洩漏相關的問題也可以順便整理一下,便有了這篇文章,也方便以後自己查閱 為什麼Handler會造成記憶體洩漏 下面是一段簡單的Handler使用 public class MainActivity extend

Android記憶體洩漏場景及解決方法

本文包括以下內容: 1. 記憶體洩漏原理 2. Android記憶體洩漏發生的情況 3. 檢測記憶體洩漏的工具、方法 4. 如何避免記憶體洩漏 更多Android面試相關請點選 - 四步準備Android面試 - Android開發概要 - 大疆提前批第一次電面

記憶體洩漏常見解決辦法

之所以撰寫這篇文章是因為前段時間花費了很大的精力在已經成熟的程式碼上再去處理memory leak問題。寫此的目的是希望我們應該養成良好的編碼習慣,儘可能的避免這樣的問題,因為當你對著一大片的程式碼再去處理此類的問題,此時無疑增加了解決的成本和難度。準確的說屬於補救措施了。 1. 什麼是記憶體

Java常見記憶體溢位異常及解決方法

Java的記憶體溢位,主要是兩方面的異常:堆記憶體溢位、非堆記憶體溢位。 一、 java.lang.OutOfMemoryError: Java heap space。 Heap size 設定 JVM堆的設定是指java程式執行過程中JVM可以調配使用的記憶體空間的設定

Git:代碼沖突常見解決方法

服務 nbsp 發布 配置文件 merge reset com main clas 如果系統中有一些配置文件在服務器上做了配置修改,然後後續開發又新添加一些配置項的時候, 在發布這個配置文件的時候,會發生代碼沖突: error: Your local changes to

有關tomcat記憶體溢位的完美解決方法

tomcat記憶體溢位設定JAVA_OPTS 設定Tomcat啟動的初始記憶體 其初始空間(即-Xms)是實體記憶體的1/64,最大空間(-Xmx)是實體記憶體的1/4。可以利用JVM提供的-Xmn -Xms -Xmx等選項可 進行設定 例項,以下給出1G記憶體環境下java

Java動態編譯優化——ZipFileIndex記憶體洩漏問題分析解決

一、前言: 前幾天解決了URLClassLoader記憶體洩漏的問題,但是解決問題就像剝洋蔥,剝去了外層,內層 問題又暴露出來了。當URLClassLoader記憶體洩漏解決, 需要解決的就是ZipFileIndex記憶體洩漏的問題了,而且這個問題折騰了我2天半的時間。 URLClass

ETL-關於緩慢變化維的幾種常見解決方法

緩慢變化維: 維度表裡面的資料並非是始終不變的,總會隨著時間發生變化。 維度建模的資料倉庫中,有一個概念叫Slowly Changing Dimensions,中文一般翻譯成“緩慢變化維”,經常被簡寫為SCD。緩慢變化維的提出是因為在現實世界中,維度的屬性並不是靜態的,它會隨著時間的流失發生

Android記憶體洩漏查詢和解決adb shell dumpsys meminfo packagement

1.通過adb shell dumpsys meminfo packageName來檢視記憶體使用狀況 在沒有開啟應用的情況下,該命令返回的資料是這樣的: 2.開啟這個應用的MainActivity,再通過命令檢視: 可以看到打印出來很多的資訊,而對於我們檢

Git:程式碼衝突常見解決方法

如果系統中有一些配置檔案在伺服器上做了配置修改,然後後續開發又新新增一些配置項的時候, 在釋出這個配置檔案的時候,會發生程式碼衝突: error: Your local changes to the following files would be overwritten

VueJS SSR 後端繪製記憶體洩漏的相關解決經驗

引言 Memory Leak 是最難排查除錯的 Bug 種類之一,因為記憶體洩漏是個 undecidable problem,只有開發者才能明確一塊記憶體是不是需要被回收。再加上記憶體洩漏也沒有特定的報錯資訊,只能通過一定時間段的日誌來判斷是否存在記憶體洩漏。大家熟悉的常用除錯工具對排查記憶體洩漏也沒有用武

Spring cloud開發記憶體佔用過高解決方法

https://blog.csdn.net/wanhuiguizong/article/details/79289986   版權宣告:本文為博主原創文章,轉載請宣告文章來源和原文連結。 https://blog.csdn.net/wanhuiguizong/article/details/79

iOS NSString 記憶體洩漏 , 求解決

遞迴, autorelease物件如何避免記憶體洩漏???? -(NSString *) getStuJsonString : (StuNode *) stuNode{     NSString *nodeJsonString = [[NSString alloc] i

Java 虛擬機器記憶體溢位問題和解決方法

一什麼是記憶體溢位 1記憶體溢位是指應用系統中存在無法回收的記憶體或使用的記憶體過多,最終使得程式執行要用到的記憶體大於虛擬機器能提供的最大記憶體。 2 Java的記憶體管理就是物件的分配和釋放問題。 在Java中,記憶體的分配是由程式完成的,而記憶體的釋

JNI記憶體釋放以及洩露處理方法彙總

在c++中new的物件,如果不返回java,必須用release掉,否則記憶體洩露。包括NewStringUTF,NewObject 。如果返回java不必release,java會自己回

Java包衝突常見解決方法

Java的好處之一是有大量的庫可供開發者使用,然而,這些庫通常都有較多版本,並且也往往會依賴其他的庫。 使用Maven或者其他構建工具時,經常需要將這些依賴打包成一個Jar包,或者自己的Jar包與其他的Jar包同時放到Classpath中。 這些時候,很容易就會產生一個常見

jmeter記憶體溢位原因及解決方法

jmeter是一個java開發的開源效能測試工具,在效能測試中可支援模擬併發壓測,但有時候當模擬併發請求較大或者指令碼執行時間較長時,壓力機會出現卡頓甚至報異常————記憶體溢位,這裡就介紹下如何解決記憶體溢位及相關的知識點。。。首先來看看我們常說的記憶體洩漏、記憶體溢位是什麼?記憶體洩露是指你的應用使用資源

Linux系統記憶體佔用90%以上——解決方法

最近遇到一個疑問,不管是top,還是cat /proc/meminfo,  發現free記憶體基本快沒了,難道我們的程式出問題了?排查半天沒有事, 後來百度到相關帖子,記錄一下,這是Linux核心機制, Linux與Windows不同,會存在快取記憶體,通常叫做Cac

malloc記憶體管理器記憶體不釋放的解決方法

        最近在對程式測試時發現,程式在執行某項操作後記憶體有一部分不會釋放,但是,在多次執行後又不會繼續增加,執行緒數越多,多次執行同樣的操作,記憶體佔用還會增大。檢查程式碼,並沒有程式碼上的記憶體洩漏,甚是苦惱。         網上搜索發現是malloc的原因: