java gc與heap內存簡述

java內存結構和gc算法有很多種,二者也是互相決定的;使用不同的gc算法的jvm會有不同的內存結構。這裏就簡單整理下常見的sun jvm內存結構和回收算法。

一、java的內存結構


java的內存分爲如上幾塊。

Virtual:如果 -Xms指定的值比-Xmx的小,那麼兩者的差值就是圖上所示的virtual區域

Perm:永久區,主要用來放JVM自己的反射對象,比如類對象和方法對象等。對於spring hibernate這些需要動態類型支持的框架,這個區域需要足夠的空間。這個區域不屬於heap中。

Young:這裏是較新的對象活躍區域。新創建的對象處於Eden區中,經過一次回收的對象會轉移到From區;From區再經過一次回收會轉移到To區;To區再經過一次回收就會移出Young區轉移至Tenured(Old)區。

Tenured(Old):這裏是經過數次GC後仍然存活的對象所處區域,池對象(如線程池)就活躍在這個區域中。


二、java gc機制

由於垃圾收集算法在各個虛擬機以及不同的平臺上會有不同的實現,所以這裏只大概記錄一下幾個基本的算法。

1.引用計數:

爲每一個對象添加一個計數器,計數器記錄了對該對象的活躍引用的數量。如果計數器爲0,則說明這個對象沒有被任何變量所引用,即應該進行垃圾收集。
收集過程如下:
1. 減少被收集對象所引用的對象的計數器的值
2.將其放入延時收集隊列之中

引用計數的方法需要編譯器的配合。編譯器需要爲此對象生成額外的代碼。如賦值函數將此對象賦值給一個引用時,需要增加此對象的引用計數。還有就是,當一個引用變量的生命週期結束時,需要更新此對象的引用計數器。

由於引用計數無法解決相互引用的情況,所以該算法並沒有被任何jvm採用。

2.標記-清除收集器

收集過程分爲2個階段
1. 首先停止所有工作,從根集遍歷所有被引用的節點,然後進行標記,最後恢復所有工作
2. 收集階段會收集那些沒有被標記的節點,然後返回空閒鏈表

標記-清除法的缺點在於
1.標記階段暫停的時間可能很長,而整個堆在交換階段又是可訪問的,可能會導致被換頁換出內存。
2.另外一個問題在於,不管你這個對象是不是可達的,即是不是垃圾,都要在清楚階段被檢查一遍,非常耗時.3,標記清楚這兩個動作會產生大量的內存碎片,於是當有大對象進行分配時,不需要觸發一次垃圾回收動作

3.拷貝收集器(Copying Collectors)(適用於young generation:PSYoungGen)

    該算法的提出是爲了克服句柄的開銷和解決堆碎片的垃圾回收。
    將內存分爲兩個區域(from space和to space)。所有的對象分配內存都分配到from space。在清理非活動對象階段,把所有標誌爲活動的對象,copy到to space,之後清楚from space空間。然後互換from sapce和to space的身份。既原先的from space變成to sapce,原先的to space變成from space。每次清理,重複上述過程。

  現在的商業虛擬機都用這種算法來回收新生代,因爲新生代的大多數的生命週期都很短暫,所以前面提到的兩塊相互切換的區域並不需要按照1:1來進行分配。而是分配了一個Eden區,兩個Survivor區。大部分對象默認的都是在 Eden區中生成。當垃圾回收時,Eden和其中的一個Survivor區的存活對象將被複制到另外一個Survivor區,當另外一個Survivor區也滿了的時候,從Eden和第一個Survivor區複製過來的並且此時還存活的對象,將被複制到tenured generation。需要注意,Survivor的兩個區是對稱的,沒先後關係,所以同一個區中可能同時存在從Eden複製過來對象,和從前一個Survivor複製過來的對象,而複製到年老區的只有從第一個Survivor去過來的對象。而且,Survivor區總有一個是空的。
    young generation的gc稱爲minor gc。經過數次minor gc,依舊存活的對象,將被移出young generation,移到tenured generation

優點:copy算法不理會非活動對象,copy數量僅僅取決爲活動對象的數量。並且在copy的同時,整理了heap空間,即,to space的空間使用始終是連續的,內存使用效率得到提高。
缺點:默認情況下Eden:Survivor=8:1, 所以總會有100-(80+10)%的新生代內存會被浪費掉。

4.標記-整理收集器(Mark-Compact Collectors)(適用於存放生命週期較長對象的tenured generation:PSOldGen)

標記整理收集器,通過融合了標記-清除收集器和拷貝收集器的優點,很好的解決了拷貝收集策略中,堆內存浪費嚴重的問題。

標記整理收集器分爲2個階段
1. 標記階段, 這個階段和標記-清除收集器的標記階段相同
2. 整理階段, 這個階段將所有做了標記的活動對象整理到堆的底部(有點像是磁盤碎片整理,呵呵)

    生命週期較長的對象,歸入到tenured generation。一般是經過多次minor gc,還依舊存活的對象,將移入到tenured generation。(當然,在minor gc中如果存活的對象的超過survivor的容量,放不下的對象會直接移入到tenured generation)tenured generation的gc稱爲major gc,就是通常說的full gc。由於tenured generaion區域比較大,而且通常對象生命週期都比較長,所以這部分的gc時間比較長。
    minor gc可能引發full gc。當eden+from space的空間大於tenured generation區的剩餘空間時,會引發full gc。這是悲觀算法,要確保eden+from space的對象如果都存活,必須有足夠的tenured generation空間存放這些對象。

三、其它gc補充

商業jvm普遍使用這個方法來判斷對象是否可達:通過類的靜態變量及棧中變量依次遍歷可及的爲存貨對象。

finalize()方法:這個方法會在gc時被調用。但是gc並不保證什麼時候銷燬對象,即使調用System.gc()方法,也只是通知它進行回收,jvm並不保證立刻執行它。有時候如果在這個方法裏寫入不恰當的代碼引用了一些對象可能會導致gc永遠不會回收對象,因此不推薦在這裏進行流關閉之類c++析構函數乾的工作。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章