GC:垃圾回收站,是將java的無用的堆對象進行清理,釋放內存,以免發生內存泄露。在介紹java回收站前,首先介紹下幾種回收機制
1. 引用計數:
當一個對象A被其他對象B引用時,對象A引用+1,斷開引用則-1,GC工作時,會檢查所有對象中的引用計數,如果為0則代表要清除,>0則表示有其他對象引用不能清除。這種機制有一個致命缺點,就是當兩個對象互引用時,在遍歷時可能會發生這兩個對象引數永遠不為0,則永遠不會被刪除。此機制java從不使用。
2. 停止-復制:
程序暫停運行,啟動GC,GC從堆或靜態存儲區開始遍歷所有對象,判斷對象是否“活”的對象,如果是活不刪除,反之刪除。判斷是否“活”就是判斷該對象是否有被其他對象引用,從鏈上去查找。當是活對象時,GC會從另外堆里開避一個大空間,然后將活對象復制一份到新空間里,在復制時按着緊密排列,同時更新所有引用地址為新的地址,不活對象原封不動。遍歷完后,再遍歷一次,這次是將不活的對象徹底給刪除。
優點:所有對象能夠重新緊密排列,不會出現內存碎片,這對以后創建對象能提供更快的效率。
缺點:占用的內存空間大,要原對象的2倍空間;這種模式無論如何所有活的對象都要復制一份,假設遍歷到最后,對象很穩定,只出現少量垃圾對象或者根本沒垃圾對象,這時已經做了復制工作,浪費了資源。
3. 標記-刪除:
程序暫停運行,啟動GC,GC從堆或靜態存儲區開始遍歷所有對象,判斷對象是否“活”的對象,如果是活不刪除,反之刪除。判斷是否“活”就是判斷該對象是否有被其他對象引用,從鏈上去查找。當是活對象時,會給對象給個標記符號,死對象則不標記,遍歷完后,第二次遍歷時,只保留標記的對象,把所有沒標記的對象都刪除。
以上第2和第3種都是要遍歷兩次,而且都是最后一次才執行真正刪除,比起第1種來說要慢很多,因為要遍歷2次,尤其是第2種,還要復制動作,但這2種歸避了第1種的致命缺點。
早期的jvm采用的是標記-刪除模式,繼java5后,jvm采用自適應技術,就是根據自身狀態情況,在”停止-復制“和”標記-刪除“自動切換以達到最快效率。在第1遍歷時,如果確定對象需要回收,則會先執行對象的finalize()方法。
java GC具體工作原理:
JVM分配內存是以較大的塊為單位,如果對象為較大,則該對象本身為一個塊。GC先啟動“停止-復制”模式,遍歷對象時,遇到活對象,則把對象復制到新塊里,同時塊代數+1,如果對象較大,則保持不動,代數+1;如果檢測到垃圾對象稀少,則自動切換到“標記-刪除”模式,如果檢測到對象分布到內存太零散,則又會自動切換到“停止-復制”模式來重新整理內存緊密分布。這就是自適應技術。
JVM分配內存時,有一個堆指針,堆指針指向當前最后一個被分配的內存塊,由於“停止-復制”模式,對象大部分都是連續排列的,則堆指針移動下一格,就是尚未分配的內存,這時就不用在整個堆空間里查找未分配的內存了,這對創建速度有很大提高。
關於對象的finalize()方法:
finalize方法是指對象完成時執行的一些清理工作,是Object里的受保護方法,在外界不能調用。實質上這個方法是給GC調用的,什么時候調用以上有講。此方法是強調進一步對象需要釋放非托管對象,是一個檢測保險的作用,比如一個類里包含訪問一個流內容,如下:
class AccessStream
{
private InputStream stream;
private boolean isClose = false;
/* (non-Javadoc)
* @see java.lang.Object#finalize()
*/
@Override
protected void finalize() throws Throwable
{
if (!isClose)
{
stream.close();
}
super.finalize();
}
}
此類在操作流完后,正確作法是,應該即時關閉流(調用close());有時因為程序員某些租心大意原因,並沒及時關閉流,這時重寫finalize() 作最后保險作用,當沒有關閉時,釋放此對象前關閉流。
通常不建議重寫finalize,除了要求程序員嚴格習慣,最重要的是finalize並不能馬上執行,即使是顯式調用System.gc() 也不保證立刻執行,只能說建議GC執行,GC是否要執行,要看當前內存占用量等因素。如果finalize不能馬上執行,這就意味着本來應該早點釋放流,而出現很長時間才釋放。
