android垃圾回收機制及程式優化System.gc
1.垃圾收集演算法的核心思想
Java語言建立了垃圾收集機制,用以跟蹤正在使用的物件和發現並回收不再使用(引用)的物件。該機制可以有效防範動態記憶體分配中可能發生的兩個危險:因記憶體垃圾過多而引發的記憶體耗盡,以及不恰當的記憶體釋放所造成的記憶體非法引用。
垃圾收集演算法的核心思想是:對虛擬機器可用記憶體空間,即堆空間中的物件進行識別,如果物件正在被引用,那麼稱其為存活物件,反之,如果物件不再被引用,則為垃圾物件,可以回收其佔據的空間,用於再分配。垃圾收集演算法的選擇和垃圾收集系統引數的合理調節直接影響著系統性能,因此需要開發人員做比較深入的瞭解。
2.觸發主GC(Garbage Collector)的條件
JVM進行次GC的頻率很高,但因為這種GC佔用時間極短,所以對系統產生的影響不大。更值得關注的是主GC的觸發條件,因為它對系統影響很明顯。總的來說,有兩個條件會觸發主GC:
①當應用程式空閒時,即沒有應用執行緒在執行時,GC會被呼叫。因為GC在優先順序最低的執行緒中進行,所以當應用忙時,GC執行緒就不會被呼叫,但以下條件除外。
②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的頻率,也即增加了間歇性停頓的次數。
(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物件
基本型別變數佔用的記憶體資源比相應物件佔用的少得多,如果沒有必要,最好使用基本變數。
(6)儘量少用靜態物件變數
靜態變數屬於全域性變數,不會被GC回收,它們會一直佔用記憶體。
(7)分散物件建立或刪除的時間
集中在短時間內大量建立新物件,特別是大物件,會導致突然需要大量記憶體,JVM在面臨這種情況時,只能進行主GC,以回收記憶體或整合記憶體碎片, 從而增加主GC的頻率。集中刪除物件,道理也是一樣的。它使得突然出現了大量的垃圾物件,空閒空間必然減少,從而大大增加了下一次建立新物件時強制主GC 的機會。
-------------------------------------------------------------
很多人把Java的“效率低下”歸咎於不能自由管理記憶體,但我們也知道將記憶體管理封裝起來的好處,這裡就不贅述。
Java中的記憶體分配是隨著new一個新的物件來實現的,這個很簡單,而且也還是有一些可以“改進”記憶體回收的機制的,其中最顯眼的就是這個System.gc()函式。
乍一看這個函式似乎是可以進行垃圾回收的,可事實並不是那麼簡單。
其實這個gc()函式的作用只是提醒虛擬機器:程式設計師希望進行一次垃圾回收。但是它不能保證垃圾回收一定會進行,而且具體什麼時候進行是取決於具體的虛擬機器的,不同的虛擬機器有不同的對策。
那麼下一個問題就是:gc()進行回收的準則是什麼?也就是說什麼樣的物件可以被回收?
簡單來說就是:沒有被任何可達變數指向的物件。這裡的可達是我發明的……意思就是能夠找到的,那什麼樣的是不可達的呢?
比如說:
a.v = b;
b.v = c;
/*
*Watch out !
*/
a.v = d;
看一下這段程式碼:
第一行:物件a的變數v指向了物件b
第二行:物件b的變數v指向了物件c
第六行:物件a的變數v指向了變數d。
這個時候,雖然變數c指向的物件有c 以及b.v指向它,但是它們都已經不可達了,為什麼?因為唯一可以找到它們的是a.v,但是現在a.v指向了d,所以他們就是不可達的了。
理由也很直觀:沒有任何可達變數指向你,你還有活下去的理由嗎?你就算活下去誰能找得到你呢?
所以說,C++中將釋放了的指標置為null的習慣要保留到Java中,因為這有可能是你釋放記憶體的唯一途徑。
最後的箴言:不要頻繁使用gc函式。