對象是使用new建立的,但是並沒有與之相對應的delete操作來回收對象佔用的記憶體。當我們完成對某個對象的使用時,只需停止對該對象的引用:將我們的引用改變為指向其他對象或指向null;或者從方法中返回,使得該方法的局部變數不複存在,從而使得對這些局部變數的引用變為不指向任何對象。不再被引用的對象被稱為垃圾(garbage),尋找並回收這些對象的過程叫做記憶體回收(garbage collection) o
Java虛擬機器利用記憶體回收來保證被引用的對象將會在記憶體中保留,同時會釋放在執行代碼中通過任何引用都不可達的對象所佔用的儲存空間。這是一種強保證—如果順著從根引用(即在執行代碼中可以直接存取的引用)開始的引用鏈可以到達某個對象,那麼該對象就不會被回收。
簡言之,當我們從任何可執行代碼都無法到達某個對象時,它所佔用的空間就可以被回收。注意,我們用的是“可以”這個詞,因為記憶體空間是否回收是由記憶體回收行程來決定的,通常情況下,只有需要更多的記憶體空間或者為了避免發生記憶體溢出時,記憶體回收行程才會運行。但是程式可能在沒有發生記憶體溢出,甚至在沒有接近記憶體溢出的時候就退出了,所以可能根本就不需要執行記憶體回收。在當前執行的所有方法中,如果所有變數都不包含指向某個對象的引用,並且從這些變數出發,順著引用鏈在所有域或數組元素中也找不到對這個對象的引用,那麼我們就說這個對象是“不可達的”。
記憶體回收意味著我們永遠不必擔心出現虛懸引用(dangling reference)。在那些可以由程式員直接控制何時刪除對象的系統中,程式員可以刪除某個其他對象還在引用的對象,如果程式員刪除了這樣的對象,那麼還在引用被刪除對象的引用就會變為虛懸的,因為它們引用的是操
作系統認為是可分配的記憶體空間(但實際上該空間已經被釋放)。系統可以將這個可分配空間分配給新的對象,這樣那些原來指向該空間的引用實際上得到的對象與它們所預期的就完全不同了。在這種情況下,當程式使用儲存於這個空間中的值並將其當作它們並不屬於的對象來操作時,就可能會引起不可預知的災難。記憶體回收為我們解決了虛懸引用問題,因為所有仍然被引用的對象都不會被當作記憶體回收,所以它們所佔用的空間也不可能被釋放。記憶體回收同時還解決了意外地多次刪除同一個對象的問題—這種問題也會引發災難。 垃圾對象的回收並不需要我們的介入,但是回收垃圾會佔用一定的系統資源。大量對象的建立和回收對時間關鍵的應用會產生幹擾,因此我們在設計這種系統時,要審慎地處理建立的對象數量,以便減少要回收的垃圾數量。
記憶體回收並不能保證記憶體總是會有空間來建立新對象。例如,如果我們不停地建立對象,並把這些對象置於某個列表中,那麼當沒有足夠的空間來建立新對象,同時也沒有任何未被引用的對象時,就無法再建立新對象了。如果我們讓上述列表保持對不再需要的對象的引用,那麼就會造成記憶體流失。記憶體回收解決了很多(但並非全部)的記憶體配置問題。
與記憶體回收行程互動
儘管Java語言本身沒有任何顯式地處置空閑對象的方法,我們還是可以通過直接調用記憶體回收行程來尋找不再使用的對象。Runtime類以及system類中的一些便捷方法使得我們可以調用記憶體回收行程,請求運行所有待啟動並執行終結器,或者查看當前的記憶體狀態:
.public void gc Q:該方法請求Java虛擬機器花費精力去回收不再使用的對象,以便能夠重用這些對象所佔據的記憶體。
.public void runFinalization():該方法請求Java虛擬機器花費精力去運行如下的終結器:那些已經被發現是不可達的,但是其終結器還未執行的對象。
“public long freememory():返回系統記憶體可用位元組的估測數。
·public long total Memory ():返回系統記憶體的總位元組數。
.public long maxmemoryo:返回Java虛擬機器可用的系統記憶體的最大位元組數。如果作業系統對Java虛擬機器沒有記憶體使用量上的限制,將返回Long . MAX-VALUE. Java中沒有任何用來設定系統最大記憶體的方法,通常,Java虛擬機器是通過命令列或者其他配置選項來設定這個值的。
要調用上述方法,我們需要通過靜態方法Runtime.getRuntime來擷取對當前Runtime對象的引用。而system類支援靜態gc和runFinalization方法,它們將調用當前Runt-ime對象上的相應方法;換句話說,System.gc()與Runtime.getRuntime().gc()方法是等價的。
在調用Runtime.gc()方法時,記憶體回收行程可能並不能釋放出任何額外的記憶體,因為可能並沒有垃圾可以回收,而且並非所有的記憶體回收行程都可以按需發現可回收對象。因此調用記憶體回收行程可能不會產生任何效果。然而,在建立大量的對象之前,特別是在記憶體回收的開銷可能會對其造成影響的時間關鍵的應用中,調用Runtime.gc()方法還是可取的。執行它有兩點潛在的好處:第一點是我們在運行應用程式之前可以得到儘可能多的記憶體,第二點是我們可以降低執行任務期間記憶體回收行程啟動並執行可能性。下面的方法在運行時刻積極地釋放了可以釋放的所有空間:
public static vo記ful1GC(){ Runtime rt=Runtime.getRuntime(); long isFree=rt.freeMemory (); long wasFree; do{ wasFree=isFree; rt.runFinalization (); rt.gc(); isFree二rt.freeMemory(); }while (isFree>wasFree); }
該方法在不斷地迴圈,通過連續調用runFinalization和gc方法,freememory的值不斷地增大。當空閑記憶體的數量不再增大時,該方法的迴圈也就結束了。
我們通常不需要調用runFinalization方法,因為finalize方法是由記憶體回收行程非同步呼叫的。在某些情況下,例如某項可以由finalize方法回收的資源被耗盡時,通過調用run-Finalization來強制執行儘可能多的終結才會顯得有用。但是請記住,我們並不能保證任何等待被終結的對象都在使用這項資源,因此runFinalization可能不會有任何作用。
fullGc方法對於大多數應用程式來說都顯得過於激進。在需要強制進行記憶體回收的特殊情況下,對system.gc方法的單次調用所收集到的垃圾即便不是全部的可利用垃圾,也是其中的絕大部分,因此重複調用會降低記憶體回收的產出率,而且在許多系統中,這些重複調用是毫無產出的。