在C++語言
程序中,使用new操做符建立的對象,在使用完畢後應該經過delete操做符顯示地釋放,不然,這些對象將佔用堆空間,永遠沒有辦法獲得回收,從而引發內存空間的泄漏。以下的簡單代碼就能夠引發內存的泄漏:
void function(){ Int[] vec = new int[5]; } |
在function()方法執行完畢後,vec數組已是不可達對象,在C++語言中,這樣的對象永遠也得不到釋放,稱這種現象爲內存泄漏。
而Java是經過垃圾收集器(Garbage Collection,GC)自動管理內存的回收,程序員不須要經過調用函數來釋放內存,但它只能回收無用而且再也不被其它對象引用的那些對象所佔用的空間。在下面的代碼中,循環申請Object對象,並將所申請的對象放入一個Vector中,若是僅僅釋放對象自己,可是由於Vector仍然引用該對象,因此這個對象對GC來講是不可回收的。所以,若是對象加入到Vector後,還必須從Vector中刪除,最簡單的方法就是將Vector對象設置爲 null。
Vector v = new Vector(10); for (int i = 1; i < 100; i++) { Object o = new Object(); v.add(o); o = null; }//此時,全部的Object對象都沒有被釋放,由於變量v引用這些對象。 |
實際上無用,而還被引用的對象,GC就無能爲力了(事實上GC認爲它還有用),這一點是致使內存泄漏最重要的緣由。
Java的內存回收機制能夠形象地理解爲在堆空間中引入了重力場,已經加載的類的靜態變量和處於活動線程的堆棧空間的變量是這個空間的牽引對象。這裏牽引對象是指按照Java語言規範,即使沒有其它對象保持對它的引用也不可以被回收的對象,即Java內存空間中的本原對象。固然類可能被去加載,活動線程的堆棧也是不斷變化的,牽引對象的集合也是不斷變化的。對於堆空間中的任何一個對象,若是存在一條或者多條從某個或者某幾個牽引對象到該對象的引用鏈,則就是可達對象,能夠形象地理解爲從牽引對象伸出的引用鏈將其拉住,避免掉到回收池中;而其它的不可達對象因爲不存在牽引對象的拉力,在重力的做用下將掉入回收池。在圖1中,A、B、C、D、E、F六個對象都被牽引對象所直接或者間接地「牽引」,使得它們避免在重力的做用下掉入回收池。若是TR1-A鏈和 TR2-D鏈斷開,則A、B、C三個對象因爲失去牽引,在重力的做用下掉入回收池(被回收),D對象也是一樣的緣由掉入回收池,而F對象仍然存在一個牽引鏈(TR3-E-F),因此不會被回收,如圖二、3所示。
圖1 初始狀態
圖2 TR1-A鏈和TR2-D鏈斷開,A、B、C、D掉入回收池
圖3 A、B、C、D四個對象被回收 經過前面的介紹能夠看到,因爲採用了垃圾回收機制,任何不可達對象均可以由垃圾收集線程回收。所以一般說的Java內存泄漏實際上是指無心識的、非故意的對象引用,或者無心識的對象保持。無心識的對象引用是指代碼的開發人員原本已經對對象使用完畢,卻由於編碼的錯誤而意外地保存了對該對象的引用(這個引用的存在並非編碼人員的主觀意願),從而使得該對象一直沒法被垃圾回收器回收掉,這種原本覺得能夠釋放掉的卻最終未能被釋放的空間能夠認爲是被「泄漏了 」。 這裏經過一個例子來演示Java的內存泄漏。假設有一個日誌類Logger,其提供一個靜態的log(String msg)方法,任何其它類均可以調用Logger.Log(message)來將message的內容記錄到系統的日誌文件中。Logger類有一個類型爲HashMap的靜態變量temp,每次在執行log(message)方法的時候,都首先將message的值丟入temp中(以當前線程+當前時間爲鍵),在方法退出以前再從temp中將以當前線程和當前時間爲鍵的條目刪除。注意,這裏當前時間是不斷變化的,因此log方法在退出以前執行刪除條目的操做並不能刪除方法執行之初丟入的條目。這樣,任何一個做爲參數傳給log方法的字符串最終因爲被Logger的靜態變量temp引用,而沒法獲得回收,這種違背實現者主觀意圖的無心識的對象保持就是咱們所說的Java內存泄漏。