Rex

          ——生命不止,奮斗不息。
          posts - 27, comments - 8, trackbacks - 0, articles - 0
            BlogJava :: 首頁(yè) :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理

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

          Posted on 2006-11-22 14:13 W.R 閱讀(552) 評(píng)論(0)  編輯  收藏 所屬分類: 支持技術(shù)
          一、C++
          ?????????一般我們常說(shuō)的內(nèi)存泄漏是指堆內(nèi)存的泄漏。堆內(nèi)存是指程序從堆中分配的,大小任意的(內(nèi)存塊的大小可以在程序運(yùn)行期決定),使用完后必須顯示釋放的內(nèi)存。應(yīng)用程序一般使用malloc,realloc,new等函數(shù)從堆中分配到一塊內(nèi)存,使用完后,程序必須負(fù)責(zé)相應(yīng)的調(diào)用free或delete釋放該內(nèi)存塊,否則,這塊內(nèi)存就不能被再次使用,我們就說(shuō)這塊內(nèi)存泄漏了??慈缦乱欢蜟code:
          void?GetMemory2(char?**p,?int?num)?
          {?
          ????
          *p?=?(char?*)malloc(num);?
          }?
          void?Test(void)?
          {?
          ????char?
          *str?=?NULL;?
          ????GetMemory(
          &str,?100);?
          ????strcpy(str,?
          "hello");?
          ????printf(str);?
          }?
          執(zhí)行Test后,能夠輸出hello ;但是確實(shí)是內(nèi)存泄露了,該free一下,另外我想C++中free is better than delete!
          ?????????以下這段小程序演示了堆內(nèi)存發(fā)生泄漏的情形:
          void?MyFunction(int?nSize)
          {
          ???????
          char*??p=?new?char[nSize];
          ???????
          if(?!GetStringFrom(?p,?nSize?)?){
          ??????????????MessageBox(“Error”);
          ??????????????
          return;
          ???????}

          ???????…
          //using?the?string?pointed?by?p;
          ???????delete?p;
          }


          ?????????廣義的說(shuō),內(nèi)存泄漏不僅僅包含堆內(nèi)存的泄漏,還包含系統(tǒng)資源的泄漏(resource leak),比如核心態(tài)HANDLE,GDI Object,SOCKET, Interface等,從根本上說(shuō)這些由操作系統(tǒng)分配的對(duì)象也消耗內(nèi)存,如果這些對(duì)象發(fā)生泄漏最終也會(huì)導(dǎo)致內(nèi)存的泄漏。而且,某些對(duì)象消耗的是核心態(tài)內(nèi)存,這些對(duì)象嚴(yán)重泄漏時(shí)會(huì)導(dǎo)致整個(gè)操作系統(tǒng)不穩(wěn)定。所以相比之下,系統(tǒng)資源的泄漏比堆內(nèi)存的泄漏更為嚴(yán)重。

          GDI Object的泄漏是一種常見(jiàn)的資源泄漏:

          ?

          void?CMyView::OnPaint(?CDC*?pDC?)
          {
          ???????CBitmap?bmp;
          ???????CBitmap
          *?pOldBmp;
          ???????bmp.LoadBitmap(IDB_MYBMP);
          ???????pOldBmp?
          =?pDC->SelectObject(?&bmp?);
          ???????…
          ???????
          if(?Something()?){
          ??????????????
          return;
          ???????}

          ???????pDC
          ->SelectObject(?pOldBmp?);
          ???????
          return;
          }

          當(dāng)函數(shù)Something()返回非零的時(shí)候,程序在退出前沒(méi)有把pOldBmp選回pDC中,這會(huì)導(dǎo)致pOldBmp指向的HBITMAP對(duì)象發(fā)生泄漏。這個(gè)程序如果長(zhǎng)時(shí)間的運(yùn)行,可能會(huì)導(dǎo)致整個(gè)系統(tǒng)花屏。這種問(wèn)題在Win9x下比較容易暴露出來(lái),因?yàn)閃in9x的GDI堆比Win2k或NT的要小很多。
          ?????????有一個(gè)很簡(jiǎn)單的辦法來(lái)檢查一個(gè)程序是否有內(nèi)存泄漏.就是是用Windows的任務(wù)管理器(Task Manager).??運(yùn)行程序,然后在任務(wù)管理器里面查看 “內(nèi)存使用”和”虛擬內(nèi)存大小”兩項(xiàng),當(dāng)程序請(qǐng)求了它所需要的內(nèi)存之后,如果虛擬內(nèi)存還是持續(xù)的增長(zhǎng)的話,就說(shuō)明了這個(gè)程序有內(nèi)存泄漏問(wèn)題. 當(dāng)然如果內(nèi)存泄漏的數(shù)目非常的小,用這種方法可能要過(guò)很長(zhǎng)時(shí)間才能看的出來(lái).
          ?????????已經(jīng)有許多技術(shù)被研究出來(lái)以應(yīng)對(duì)這個(gè)問(wèn)題,比如Smart Pointer,Garbage Collection等。Smart Pointer技術(shù)比較成熟,STL中已經(jīng)包含支持Smart Pointer的class,但是它的使用似乎并不廣泛,而且它也不能解決所有的問(wèn)題;Garbage Collection技術(shù)在Java中已經(jīng)比較成熟,但是在c/c++領(lǐng)域的發(fā)展并不順暢,雖然很早就有人思考在C++中也加入GC的支持?,F(xiàn)實(shí)世界就是這樣的,作為一個(gè)c/c++程序員,內(nèi)存泄漏是你心中永遠(yuǎn)的痛。

          以發(fā)生的方式來(lái)分類,內(nèi)存泄漏可以分為4類:

          ??????1.常發(fā)性內(nèi)存泄漏。發(fā)生內(nèi)存泄漏的代碼會(huì)被多次執(zhí)行到,每次被執(zhí)行的時(shí)候都會(huì)導(dǎo)致一塊內(nèi)存泄漏。比如例二,如果Something()函數(shù)一直返回True,那么pOldBmp指向的HBITMAP對(duì)象總是發(fā)生泄漏。

          ??????2.偶發(fā)性內(nèi)存泄漏。發(fā)生內(nèi)存泄漏的代碼只有在某些特定環(huán)境或操作過(guò)程下才會(huì)發(fā)生。比如例二,如果Something()函數(shù)只有在特定環(huán)境下才返回True,那么pOldBmp指向的HBITMAP對(duì)象并不總是發(fā)生泄漏。常發(fā)性和偶發(fā)性是相對(duì)的。對(duì)于特定的環(huán)境,偶發(fā)性的也許就變成了常發(fā)性的。所以測(cè)試環(huán)境和測(cè)試方法對(duì)檢測(cè)內(nèi)存泄漏至關(guān)重要。

          ??????3.一次性內(nèi)存泄漏。發(fā)生內(nèi)存泄漏的代碼只會(huì)被執(zhí)行一次,或者由于算法上的缺陷,導(dǎo)致總會(huì)有一塊僅且一塊內(nèi)存發(fā)生泄漏。比如,在類的構(gòu)造函數(shù)中分配內(nèi)存,在析構(gòu)函數(shù)中卻沒(méi)有釋放該內(nèi)存,但是因?yàn)檫@個(gè)類是一個(gè)Singleton,所以內(nèi)存泄漏只會(huì)發(fā)生一次。另一個(gè)例子:

          ?

          char*?g_lpszFileName?=?NULL;
          void?SetFileName(?const?char*?lpcszFileName?)
          {
          ????
          if(?g_lpszFileName?){
          ????????free(?g_lpszFileName?);
          ????}

          ????g_lpszFileName?
          =?strdup(?lpcszFileName?);
          }

          如果程序在結(jié)束的時(shí)候沒(méi)有釋放g_lpszFileName指向的字符串,那么,即使多次調(diào)用SetFileName(),總會(huì)有一塊內(nèi)存,而且僅有一塊內(nèi)存發(fā)生泄漏。

          ??????4.隱式內(nèi)存泄漏。程序在運(yùn)行過(guò)程中不停的分配內(nèi)存,但是直到結(jié)束的時(shí)候才釋放內(nèi)存。嚴(yán)格的說(shuō)這里并沒(méi)有發(fā)生內(nèi)存泄漏,因?yàn)樽罱K程序釋放了所有申請(qǐng)的內(nèi)存。但是對(duì)于一個(gè)服務(wù)器程序,需要運(yùn)行幾天,幾周甚至幾個(gè)月,不及時(shí)釋放內(nèi)存也可能導(dǎo)致最終耗盡系統(tǒng)的所有內(nèi)存。所以,我們稱這類內(nèi)存泄漏為隱式內(nèi)存泄漏。舉一個(gè)例子:

          ?

          class?Connection
          {
          public:
          ???????Connection(?SOCKET?s);
          ???????
          ~Connection();
          ???????…
          private:
          ???????SOCKET?_socket;
          ???????…
          }
          ;
          class?ConnectionManager
          {
          public:
          ???????ConnectionManager()
          {

          ???????}

          ???????
          ~ConnectionManager(){
          ??????????list
          <Connection>::iterator?it;
          ??????????
          for(?it?=?_connlist.begin();?it?!=?_connlist.end();?++it?){
          ?????????????????????delete?(
          *it);
          ??????????????}

          ??????????????_connlist.clear();
          ???????}

          ???????
          void?OnClientConnected(?SOCKET?s?){
          ?????????Connection
          *?p?=?new?Connection(s);
          ?????????_connlist.push_back(p);
          ???????}

          ???????
          void?OnClientDisconnected(?Connection*?pconn?){
          ??????????????_connlist.remove(?pconn?);
          ??????????????delete?pconn;
          ???????}

          private:
          ???????list
          <Connection*>?_connlist;
          }
          ;

          ?假設(shè)在Client從Server端斷開(kāi)后,Server并沒(méi)有呼叫OnClientDisconnected()函數(shù),那么代表那次連接的Connection對(duì)象就不會(huì)被及時(shí)的刪除(在Server程序退出的時(shí)候,所有Connection對(duì)象會(huì)在ConnectionManager的析構(gòu)函數(shù)里被刪除)。當(dāng)不斷的有連接建立、斷開(kāi)時(shí)隱式內(nèi)存泄漏就發(fā)生了。

          二、JAVA中的內(nèi)存泄露
          ?????????JAVA有GC自動(dòng)回收內(nèi)存,內(nèi)存泄露是指系統(tǒng)中存在無(wú)法回收的內(nèi)存,有時(shí)候會(huì)造成內(nèi)存不足或系統(tǒng)崩潰。在C/C++中分配了內(nèi)存不釋放的情況就是內(nèi)存泄露。雖然Java存在內(nèi)存泄露,但是基本上不用很關(guān)心它,特別是那些對(duì)代碼本身就不講究的就更不要去關(guān)心這個(gè)了。 Java中的內(nèi)存泄露當(dāng)然是指:存在無(wú)用但是垃圾回收器無(wú)法回收的對(duì)象。而且即使有內(nèi)存泄露問(wèn)題存在,也不一定會(huì)表現(xiàn)出來(lái)。看下面的例子:

          public?class?Stack?{
           
          private?Object[]?elements=new?Object[10];
           
          private?int?size?=?0;?
           
          public?void?push(Object?e){
            ensureCapacity();
            elements[size
          ++]?=?e;?
           }

           
          public?Object?pop(){
            
          if(?size?==?0)?
             
          throw?new?EmptyStackException();?
             
          return?elements[--size];
           }

          ??
          private?void?ensureCapacity(){
           ??
          if(elements.length?==?size){
            ??Object[]?oldElements?
          =?elements;
            ??elements?
          =?new?Object[2?*?elements.length+1];
            ??System.arraycopy(oldElements,
          0,?elements,?0,?size);
           ??}

          ??}

          }

          假如堆棧加了10個(gè)元素,然后全部彈出來(lái),雖然堆棧是空的,沒(méi)有我們要的東西,但是這是個(gè)對(duì)象是無(wú)法回收的,這個(gè)才符合了內(nèi)存泄露的兩個(gè)條件:無(wú)用,無(wú)法回收。再看這個(gè)例子:
          public?class?Bad{
           public?static?Stack?s
          =Stack();
            static{
             s.push(new?Object());
             s.pop();?
          //這里有一個(gè)對(duì)象發(fā)生內(nèi)存泄露
             s.push(new?Object());?
          //上面的對(duì)象可以被回收了,等于是自愈了
            }
          }?
          因?yàn)槭莝tatic,就一直存在到程序退出,但是我們也可以看到它有自愈功能,就是說(shuō)如果你的Stack最多有100個(gè)對(duì)象,那么最多也就只有100個(gè)對(duì)象無(wú)法被回收其實(shí)這個(gè)應(yīng)該很容易理解,Stack內(nèi)部持有100個(gè)引用,最壞的情況就是他們都是無(wú)用的,因?yàn)槲覀円坏┓判碌倪M(jìn)取,以前的引用自然消失!for example:
          public?class?NotTooBad{
           public?void?doSomething(){
            Stack?s
          =new?Stack();
            s.push(new?Object());
            
          //other?code
            s.pop();
          //這里同樣導(dǎo)致對(duì)象無(wú)法回收,內(nèi)存泄露.
           }
          //退出方法,s自動(dòng)無(wú)效,s可以被回收,Stack內(nèi)部的引用自然沒(méi)了,所以
           
          //這里也可以自愈,而且可以說(shuō)這個(gè)方法不存在內(nèi)存泄露問(wèn)題,不過(guò)是晚一點(diǎn)
           
          //交給GC而已,因?yàn)樗欠忾]的,對(duì)外不開(kāi)放,可以說(shuō)上面的代碼99.9999%
           
          //情況是不會(huì)造成任何影響的,當(dāng)然你寫(xiě)這樣的代碼不會(huì)有什么壞的影響,但是
           
          //絕對(duì)可以說(shuō)是垃圾代碼!沒(méi)有矛盾吧,我在里面加一個(gè)空的for循環(huán)也不會(huì)有
           
          //什么太大的影響吧,你會(huì)這么做嗎?
          }
          上面兩個(gè)例子都不過(guò)是小打小鬧,但是C/C++中的內(nèi)存泄露就不是Bad了,而是Worst了。他們?nèi)绻惶帥](méi)有回收就永遠(yuǎn)無(wú)法回收,頻繁的調(diào)用這個(gè)方法內(nèi)存不就用光了!因?yàn)镴ava還有自愈功能(我自己起的名字,還沒(méi)申請(qǐng)專利),所以Java的內(nèi)存泄露問(wèn)題幾乎可以忽略了,但是知道的人就不要犯了。
            不知者無(wú)罪!Java存在內(nèi)存泄露,但是也不要夸大其辭。如果你對(duì)Java都不是很熟,你根本就不用關(guān)心這個(gè),我說(shuō)過(guò)你無(wú)意中寫(xiě)出內(nèi)存泄露的例子就像你中一千萬(wàn)一樣概率小,開(kāi)玩笑了,其實(shí)應(yīng)該是小的多的多!?
          ?????????在某些時(shí)候,因?yàn)榇a上寫(xiě)的有問(wèn)題,會(huì)導(dǎo)致某些內(nèi)存想回收都收不回來(lái),比如下面的代碼
          Temp1?=?new?BYTE[100];
          Temp2?
          =?new?BYTE[100];
          Temp2?
          =?Temp1;
          這樣,Temp2的內(nèi)存地址就丟掉了,而且永遠(yuǎn)都找不回了,這個(gè)時(shí)候Temp2的內(nèi)存空間想回收都沒(méi)有辦法.

          主站蜘蛛池模板: 监利县| 玉林市| 嘉善县| 乌拉特后旗| 精河县| 长白| 酒泉市| 永安市| 收藏| 铁岭市| 开远市| 奇台县| 克什克腾旗| 东莞市| 南木林县| 马鞍山市| 建瓯市| 白山市| 宣城市| 永嘉县| 和林格尔县| 马鞍山市| 阿拉善盟| 四平市| 萨迦县| 东源县| 福安市| 太原市| 高陵县| 长汀县| 台北县| 田林县| 乐清市| 安宁市| 牡丹江市| 南澳县| 噶尔县| 航空| 舒兰市| 永新县| 威远县|