關於內存在一塊其實我並不是很想拿出來說,一般情況下內存這一塊都是可優化的,可以通過硬體資源或者調整一些系統或者應用系統的參數配置來進行優化。

很多同僚問到了「內存泄漏」和「內存溢出」,其實這個在baidu上就有解釋。而我們很多人經常會混淆了這兩個東西,在這裡我就簡單的引用一些資源說一下他們。

內存泄漏

  內存泄漏也稱作「存儲滲漏」,用動態存儲分配函數動態開闢的空間,在使用完畢後未釋放,結果導致一直佔據該內存單元。直到程序結束。(其實說白了就是該內存空間使用完畢之後未回收)即所謂內存泄漏。

  內存泄漏形象的比喻是「操作系統可提供給所有進程的存儲空間正在被某個進程榨乾」,最終結果是程序運行時間越長,佔用存儲空間越來越多,最終用盡全部存儲空間,整個系統崩潰。所以「內存泄漏」是從操作系統的角度來看的。這裡的存儲空間並不是指物理內存,而是指虛擬內存大小,這個虛擬內存大小取決於磁碟交換區設定的大小。由程序申請的一塊內存,如果沒有任何一個指針指向它,那麼這塊內存就泄漏。

表象

  性能測試的時候發生內存泄漏的表象就是可用內存逐漸減少,一直不釋放。

內存泄漏分類

  以發生的方式來分類,內存泄漏可以分為4類

常發性

  發生內存泄漏的代碼會被多次執行到,每次被執行的時候都會導致一塊內存泄漏。

偶發性

  發生內存泄漏的代碼只有在某些特定環境或操作過程下才會發生。常發性和偶發性是相對的。對於特定的環境,偶發性的也許就變成了常發性的。所以測試環境和測試方法對檢測內存泄漏至關重要。

一次性

  發生內存泄漏的代碼只會被執行一次,或者由於演算法上的缺陷,導致總會有一塊且僅一塊內存發生泄漏。比如,在類的構造函數中分配內存,在析構函數中卻沒有釋放該內存,所以內存泄漏只會發生一次。

隱式

  程序在運行過程中不停的分配內存,但是直到結束的時候才釋放內存。嚴格的說這裡並沒有發生內存泄漏,因為最終程序釋放了所有申請的內存。但是對於一個伺服器程序,需要運行幾天、幾周甚至幾個月,不及時釋放內存也可能導致最終耗盡系統的所有內存。所以,我們稱這類內存泄漏為隱式內存泄漏。

危害

  內存泄漏或者是說,資源耗盡後,系統會表現出什麼現象啊?  cpu資源耗盡:估計是機器沒有反應了,鍵盤,滑鼠,以及網路等等。在中了計算機病毒的設備上非常常見。  進程id耗盡:沒法創建新的進程了,串口或者telnet都沒法創建了。  硬碟耗盡: 機器要死了,交換內存沒法用,日誌也沒法用了,死是很正常的。  內存泄漏或者內存耗盡:新的連接無法創建,free的內存比較少。發生內存泄漏的程序很多,但是要想產生一定的後果,就需要這個進程是無限循環的,是個服務進程。當然,內核也是無限循環的,所以,如果內核發生了內存泄漏,情況就更加不妙。內存泄漏是一種很難定位和跟蹤的錯誤,目前還沒看到有什麼好用的工具(當然,用戶空間有一些工具,有靜態分析的,也會動態分析的,但是找內核的內存泄漏,沒有好的開源工具)。  內存泄漏和對象的引用計數有很大的關係,再加上c/c++都沒有自動的垃圾回收機制,如果沒有手動釋放內存,問題就會出現。如果要避免這個問題,還是要從代碼上入手,良好的編碼習慣和規範,是避免錯誤的不二法門。  一般我們常說的內存泄漏是指堆內存的泄漏。  堆內存是指程序從堆中分配的,大小任意的(內存塊的大小可以在程序運行期決定),使用完後必須顯式釋放的內存。  應用程序一般使用malloc,realloc,new等函數從堆中分配到一塊內存,使用完後,程序必須負責相應的調用free或delete釋放該內存塊,否則,這塊內存就不能被再次使用,我們就說這塊內存泄漏了。

部分檢測工具

  1.ccmalloc-Linux和Solaris下對C和C++程序的簡單的使用內存泄漏和malloc調試庫。  2.Dmalloc-Debug Malloc Library.  3.Electric Fence-Linux分發版中由Bruce Perens編寫的malloc()調試庫。  4.Leaky-Linux下檢測內存泄漏的程序。  5.LeakTracer-Linux、Solaris和HP-UX下跟蹤和分析C++程序中的內存泄漏。  6.MEMWATCH-由Johan Lindh編寫,是一個開放源代碼C語言內存錯誤檢測工具,主要是通過gcc的precessor來進行。  7.Valgrind-Debugging and profiling Linux programs, aiming at programs written in C and C++.  8.KCachegrind-A visualization tool for the profiling data generated by Cachegrind and Calltree.

  9.IBM Rational PurifyPlus-幫助開發人員查明C/C++、託管.NET、Java和VB6代碼中的性能和可靠性錯誤。PurifyPlus 將內存錯誤和泄漏檢測、應用程序性能描述、代碼覆蓋分析等功能組合在一個單一、完整的工具包中。

  10.ParasoftInsure++-針對C/C++應用的運行時錯誤自動檢測工具,它能夠自動監測C/C++程序,發現其中存在著的內存破壞、內存泄漏、指針錯誤和I/O等錯誤。並通過使用一系列獨特的技術(SCI技術和變異測試等),徹底的檢查和測試我們的代碼,精確定位錯誤的準確位置並給出詳細的診斷信息。能作為MicrosoftVisual C++的一個插件運行。  11.Compuware DevPartner for Visual C++ BoundsChecker Suite-為C++開發者設計的運行錯誤檢測和調試工具軟體。作為Microsoft Visual Studio和C++ 6.0的一個插件運行。  12.Electric Software GlowCode-包括內存泄漏檢查,code profiler,函數調用跟蹤等功能。給C++和.Net開發者提供完整的錯誤診斷,和運行時性能分析工具包。  13.Compuware DevPartner Java Edition-包含Java內存檢測,代碼覆蓋率測試,代碼性能測試,線程死鎖,分散式應用等幾大功能模塊。  14.Quest JProbe-分析Java的內存泄漏。  15.ej-technologies JProfiler-一個全功能的Java剖析工具,專用於分析J2SE和J2EE應用程序。它把CPU、執行緒和內存的剖析組合在一個強大的應用中。  16.BEAJRockit-用來診斷Java內存泄漏並指出根本原因,專門針對Intel平臺並得到優化,能在Intel硬體上獲得最高的性能。

內存溢出

  (out of memory)通俗理解就是內存不夠,通常在運行大型軟體或遊戲時,軟體或遊戲所需要的內存遠遠超出了你主機內安裝的內存所承受大小,就叫內存溢出。此時軟體或遊戲就運行不了,系統會提示內存溢出,有時候會自動關閉軟體,重啟電腦或者軟體後釋放掉一部分內存又可以正常運行該軟體或遊戲一段時間。

表象

  常見out of memory 錯誤

產生原因

  內存溢出是指應用系統中存在無法回收的內存或使用的內存過多,最終使得程序運行要用到的內存大於虛擬機能提供的最大內存。為瞭解決Java中內存溢出問題,我們首先必須瞭解Java是如何管理內存的。Java的內存管理就是對象的分配和釋放問題。在Java中,內存的分配是由程序完成的,而內存的釋放是由垃圾收集器(GarbageCollection,GC)完成的,程序員不需要通過調用GC函數來釋放內存,因為不同的JVM實現者可能使用不同的演算法管理GC,有的是內存使用到達一定程度時,GC才開始工作,也有定時執行的,有的是中斷式執行GC。但GC只能回收無用並且不再被其它對象引用的那些對象所佔用的空間。Java的內存垃圾回收機制是從程序的主要運行對象開始檢查引用鏈,當遍歷一遍後發現沒有被引用的孤立對象就作為垃圾回收。  引起內存溢出的原因有很多種,常見的有以下幾種:  (1)內存中載入的數據量過於龐大,如一次從資料庫取出過多數據;  (2)集合類中有對對象的引用,使用完後未清空,使得JVM不能回收;

  (3)代碼中存在死循環或循環產生過多重複的對象實體;

  (4)使用的第三方軟體中的BUG;  (5)啟動參數設定的過小;

解決方法

  內存溢出雖然很棘手,但也有相應的解決辦法,可以按照從易到難,一步步的解決。  第一步,就是修改JVM啟動參數,直接增加內存。這一點看上去似乎很簡單,但很容易被忽略。JVM默認可以使用的內存為64M,Tomcat默認可以使用的內存為128MB,對於稍複雜一點的系統就會不夠用。在某項目中,就因為啟動參數使用的默認值,經常報「OutOfMemory」錯誤。因此,-Xms,-Xmx參數一定不要忘記加。  第二步,檢查錯誤日誌,查看「OutOfMemory」錯誤前是否有其它異常或錯誤。在一個項目中,使用兩個資料庫連接,其中專用於發送簡訊的資料庫連接使用DBCP連接池管理,用戶為不將簡訊發出,有意將資料庫連接用戶名改錯,使得日誌中有許多資料庫連接異常的日誌,一段時間後,就出現「OutOfMemory」錯誤。經分析,這是由於DBCP連接池BUG引起的,資料庫連接不上後,沒有將連接釋放,最終使得DBCP報「OutOfMemory」錯誤。經過修改正確資料庫連接參數後,就沒有再出現內存溢出的錯誤。  查看日誌對於分析內存溢出是非常重要的,通過仔細查看日誌,分析內存溢出前做過哪些操作,可以大致定位有問題的模塊。  第三步,安排有經驗的編程人員對代碼進行走查和分析,找出可能發生內存溢出的位置。重點排查以下幾點:

  (1)檢查代碼中是否有死循環或遞歸調用。

  (2)檢查是否有大循環重複產生新對象實體。  (3)檢查對資料庫查詢中,是否有一次獲得全部數據的查詢。一般來說,如果一次取十萬條記錄到內存,就可能引起內存溢出。這個問題比較隱蔽,在上線前,資料庫中數據較少,不容易出問題,上線後,資料庫中數據多了,一次查詢就有可能引起內存溢出。因此對於資料庫查詢盡量採用分頁的方式查詢。  (4)檢查List、MAP等集合對象是否有使用完後,未清除的問題。List、MAP等集合對象會始終存有對對象的引用,使得這些對象不能被GC回收。  第四步,使用內存查看工具動態查看內存使用情況。某個項目上線後,每次系統啟動兩天後,就會出現內存溢出的錯誤。這種情況一般是代碼中出現了緩慢的內存泄漏,用上面三個步驟解決不了,這就需要使用內存查看工具了。  內存查看工具有許多,比較有名的有:Optimizeit Profiler、JProbeProfiler、JinSight和Java1.5的Jconsole等。它們的基本工作原理大同小異,都是監測Java程序運行時所有對象的申請、釋放等動作,將內存管理的所有  信息進行統計、分析、可視化。開發人員可以根據這些信息判斷程序是否有內存泄漏問題。一般來說,一個正常的系統在其啟動完成後其內存的佔用量是基本穩定的,而不應該是無限制的增長的。持續地觀察系統運行時使用的內存的大小,可以看到在內存使用監控窗口中是基本規則的鋸齒形的圖線,如果內存的大小持續地增長,則說明系統存在內存泄漏問題。通過間隔一段時間取一次內存快照,然後對內存快照中對象的使用與引用等信息進行比對與分析,可以找出是哪個類的對象在泄漏。  通過以上四個步驟的分析與處理,基本能處理內存溢出的問題。當然,在這些過程中也需要相當的經驗與敏感度,需要在實際的開發與調試過程中不斷積累。

緩衝區溢出

  為了便於理解,我們不妨打個比方。緩衝區溢出好比是將十磅的糖放進一個只能裝五磅的容器裏。一旦該容器放滿了,餘下的部分就溢出在櫃檯和地板上,弄得一團糟。由於計算機程序的編寫者寫了一些編碼,但是這些編碼沒有對目的區域或緩衝區——五磅的容器——做適當的檢查,看它們是否夠大,能否完全裝入新的內容——十磅的糖,結果可能造成緩衝區溢出的產生。如果打算被放進新地方的數據不適合,溢得到處都是,該數據也會製造很多麻煩。但是,如果緩衝區僅僅溢出,這只是一個問題。到此時為止,它還沒有破壞性。當糖溢出時,櫃檯被蓋住。可以把糖擦掉或用吸塵器吸走,還櫃檯本來面貌。與之相對的是,當緩衝區溢出時,過剩的信息覆蓋的是計算機內存中以前的內容。除非這些被覆蓋的內容被保存或能夠恢復,否則就會永遠丟失。

  在丟失的信息裏有能夠被程序調用的子程序的列表信息,直到緩衝區溢出發生。另外,給那些子程序的信息——參數——也丟失了。這意味著程序不能得到足夠的信息從子程序返回,以完成它的任務。就像一個人步行穿過沙漠。如果他依賴於他的足跡走回頭路,當沙暴來襲抹去了這些痕跡時,他將迷失在沙漠中。這個問題比程序僅僅迷失方向嚴重多了。入侵者用精心編寫的入侵代碼(一種惡意程序)使緩衝區溢出,然後告訴程序依據預設的方法處理緩衝區,並且執行。此時的程序已經完全被入侵者操縱了。  入侵者經常改編現有的應用程序運行不同的程序。例如,一個入侵者能啟動一個新的程序,發送祕密文件(支票本記錄,口令文件,或財產清單)給入侵者的電子郵件。這就好像不僅僅是沙暴吹了腳印,而且後來者也會踩出新的腳印,將我們的迷路者領向不同的地方,他自己一無所知的地方。

緩衝處理

  你屋子裡的門和窗戶越少,入侵者進入的方式就越少……  由於緩衝區溢出是一個編程問題,所以只能通過修復被破壞的程序的代碼而解決問題。如果你沒有源代碼,從上面「堆棧溢出攻擊」的原理可以看出,要防止此類攻擊,我們可以:  1、開放程序時仔細檢查溢出情況,不允許數據溢出緩衝區。由於編程和編程語言的原因,這非常困難,而且不適合大量已經在使用的程序;  2、使用檢查堆棧溢出的編譯器或者在程序中加入某些記號,以便程序運行時確認禁止黑客有意造成的溢出。問題是無法針對已有程序,對新程序來講,需要修改編譯器;  3、經常檢查你的操作系統和應用程序提供商的站點,一旦發現他們提供的補丁程序,就馬上下載並且應用在系統上,這是最好的方法。但是系統管理員總要比攻擊者慢一步,如果這個有問題的軟體是可選的,甚至是臨時的,把它從你的系統中刪除。舉另外一個例子,你屋子裡的門和窗戶越少,入侵者進入的方式就越少。  扯遠了,這些是安全性測試方面的內容。不過性能測試方面有時候也得注意這些內容。性能測試的時候如果應用程序不報錯,並且系統的可用內存不是在不斷減少,就可以認為內存正常。
推薦閱讀:
相關文章