1.垃圾收集演算法的核心思想
Java語言建立了垃圾收集機制,以追蹤正在使用的物件和發現並回收不再使用(引用)的物件。此機制可以有效防範動態記憶體分配中可能發生的兩個危險:因記憶體垃圾過多而引發的記憶體耗盡,以及不恰當的記憶體釋放所造成的記憶體非法引用。
垃圾收集演算法的核心思想是:對虛擬機器可用記憶體空間,即堆空間中的對象進行識別,如果對象正在被引用,那麼稱其為存活對象,反之,如果對像不再被引用,則為垃圾對象,可以回收其佔據的空間,用於再分配。垃圾收集演算法的選擇和垃圾收集系統參數的合理調節直接影響系統效能,因此需要開發人員做比較深入的了解。
2.觸發主GC(Garbage Collector)的條件
JVM進行次GC的頻率很高,但因為這種GC佔用時間極短,所以對系統產生的影響不大。更值得關注的是主GC的觸發條件,因為它對系統影響很明顯。總的來說,有兩個條件會觸發主GC:
(1)當應用程式空閒時,即沒有應用執行緒在運行時,GC會被呼叫。因為GC在優先權最低的執行緒中進行,所以當應用忙時,GC執行緒就不會被呼叫,但以下條件除外。
(2)Java堆記憶體不足時,GC會被呼叫。當應用線程在運行,並在運行過程中創建新對象,若這時內存空間不足,JVM就會強制地調用GC線程,以便回收內存用於新的分配。若GC一次之後仍無法滿足記憶體分配的要求,JVM會再進行兩次GC作進一步的嘗試,若仍無法滿足要求,則 JVM將報「out of memory」的錯誤,Java應用將停止。
由於是否進行主GC由JVM根據系統環境決定,而係統環境在不斷的變化當中,所以主GC的運行具有不確定性,無法預計它何時必然出現,但可以確定的是對一個長期運行的應用來說,其主GC是反覆進行的。
3.減少GC開銷的措施
根據上述GC的機制,程序的運行會直接影響系統環境的變化,從而影響GC的觸發。若不針對GC的特性進行設計與編碼,就會出現內存駐留等一系列負面影響。為了避免這些影響,基本的原則就是盡可能地減少垃圾和減少GC過程中的開銷。具體措施包括以下幾個方面:
(1)不要明確調用System.gc()
此函數建議JVM進行主GC,雖然只是建議而非一定,但很多情況下它會觸發主GC,從而增加主GC的頻率,也即增加了間歇性停頓的次數。這裡特別要說明的是,在程式碼中顯示的呼叫System.gc(),並不一定能夠進行GC,這個我們可以透過finalize()方法來驗證,即主動呼叫System.gc(),並不一定每次都呼叫finalize()方法。 finalize()方法的特徵是在物件被回收之前, 首先呼叫finalize()方法。
(2)盡量減少臨時物件的使用
臨時物件在跳出函數呼叫後,會成為垃圾,少用臨時變數就相當於減少了垃圾的產生,從而延長了出現上述第二個觸發條件出現的時間,減少了主GC的機會。
(3)物件不用時最好明確置為Null
一般而言,為Null的物件都會被當作垃圾處理,所以將不用的物件顯式地設為Null,有利於GC收集器判定垃圾,從而提高了GC的效率。
(4)盡量使用StringBuffer,而不用String來累加字串(詳見blog另一篇文章JAVA中String與StringBuffer)
由於String是固定長的字串物件,累加String物件時,並非在一個String物件中擴增,而是重新創建新的String物件,如Str5=Str1+Str2+Str3+Str4,這條語句執行過程中會產生多個垃圾物件,因為對次作「+」操作時都必須創建新的String物件,但這些過渡物件對系統來說是沒有實際意義的,只會增加更多的垃圾。避免這種情況可以改用StringBuffer來累加字串,因StringBuffer 是可變長的,它在原有基礎上進行擴增,不會產生中間物件。
(5)能用基本類型如Int,Long,就不用Integer,Long物件
基本類型變數佔用的記憶體資源比相應物件佔用的少得多,如果沒有必要,最好使用基本變數。什麼情況下需要使用Integer?
(6)盡量少用靜態物件變數
靜態變數屬於全域變數,不會被GC回收,它們會一直佔用記憶體。
(7)分散對象創建或刪除的時間
集中在短時間內大量創建新對象,特別是大對象,會導致突然需要大量內存,JVM在面臨這種情況時,只能進行主GC,以回收記憶體或整合記憶體碎片, 從而增加主GC的頻率。集中刪除物件,道理也是一樣的。它使得突然出現了大量的垃圾物件,空閒空間必然減少,從而大大增加了下一次創建新物件時強制主GC 的機會。
4.垃圾回收演算法
(1)引用計數收集器
引用計數是垃圾收集的早期策略。在這種方法中,堆中每一個物件都有一個引用計數。當一個物件被創建了,並且指向該物件的引用被分配給一個變量,這個物件的引用計數被設定為1。例如新建一個物件A a=new A();然後a被指派給另外一個變數b,也就是b=a;那麼物件a的參考計數+1。當任何其他變數被賦值為對這個物件的引用時,計數加1。當一個物件的引用超過生存期或被設定一個新的值時,物件的引用計數會減1,例如令b=c,則a的引用計數-1。任何引用計數為0的物件可以被當作垃圾收集。當一個物件被垃圾收集的時候,它所引用的任何物件計數減1。在這種方法中,一個物件被垃圾收集後可能導致後續其他物件的垃圾收集行動。例如A a=new A();b=a;當b被垃圾回收以後,a的引用數變成0,這樣導致a也被垃圾回收。
方法的好處:引用計數收集器可以很快執行,交織在程式的運作中。這個提醒對於程式不能被長時間打斷的即時環境很有利。
方法的壞處:引用計數無法偵測出循環(即兩個或更多的物件互相引用)。循環的例子如,父物件有一個子物件的引用,子物件反過來引用父物件。這樣物件使用者都不可能數為0,就算它們已經無法被執行程式的根物件觸及。還有一個壞處就是,每次引用計數的增加或減少都帶來額外的開銷。
(2)追蹤收集器
垃圾偵測通常透過建立一個根物件的集合並且檢查從這些根物件開始的可觸及性來實現。如果正在執行的程式可以存取到的根物件和某個物件之間存在引用路徑,這個物件就是可觸及的。對於程式來說,根物件總是可以存取的。從這些根物件開始,任何可以被觸及的物件都是被認為是「活動」的物件。無法被觸及的物件被認為是垃圾,因為它們不在影響程式的未來執行。
追蹤收集器是追蹤從根結點開始的物件參考圖。在追蹤過程中遇到的物件以某手方式打上標記。總的來說,要么在物件本身上設置標記,要么用一個獨立的點陣圖來設定標記。當追蹤結束時,未被標記的物件就是無法觸及的,從而可以被收集。
基本的追蹤演算法被稱作「標記並清除」。這個名字指出垃圾手機的兩個階段。在標記階段,垃圾收集器遍歷引用樹,標記每一個遇到的物件。在清除階段,未被標記的物件被釋放,釋放物件後獲得的記憶體被返回到正在執行的程式。在Java虛擬機器中,清除步驟必須包含物件的終點。
更多深入java垃圾回收的詳解相關文章請關注PHP中文網!