寫在前面:GC的主要對象是虛擬機運行時數據區域堆內存中的對象實例,從內存回收角度看,因爲如今的收集器基本都採用分代收集(Generational Collection)算法,所以java堆中內存還能夠進一步細分:新生代和老年代。針對新生代與老年代的各自特徵,爲更好地提升收集效率及產生更少(甚至不產生)內存碎片,收集器使用的收集算法也並不相同。html
宏觀角度講,GC作的事情能夠從三方面看:java
(1)what :收集哪些對象實例?算法
(2)how:怎樣回收?函數
(3)when:什麼時候回收?線程
收集哪些對象實例?指針
查找哪些對象待回收的算法通常有兩種:a、引用計數法 b、可達性算法htm
a、引用計數法:對象
思想:當一個對象被另一個對象引用時,則該對象的引用計數器值加1,當引用鏈失效時計數器值減1,當GC被觸發時,若是對象引用計數器值爲0則表示該對象能夠回收。blog
實現:實現方式主要有兩種:一種是侵入式,一種是非侵入式 ;侵入式表示計數器所需內存在該對象內存中分配,或者說計數器變量被定義在該對象中,在對象的構造函數和複製函數中對計數器加1,在對象的析構函數中減1(C語言爲例)。非侵入式的不一樣地方是引用計數器在單獨的內存中管理。隊列
優勢:實現簡單
不足:很難解決對象間循環引用的問題
b、可達性算法:
思想:經過一系列稱爲"GC Roots"的對象做爲起始點,從這些節點開始向下搜索,所走過的路經稱爲引用鏈(Reference Chain),當一個對象到GC Roots沒有任何引用鏈相連時,則證實此對象是不可用的。
在Java語言中,可做爲GC Roots的對象包括下面幾種:
不難發現上面的幾種對象都是對象引用間的頂級對象。
在主流的商用程序語言的主流實現中,都是使用可達性分析來斷定對象是否存活的。當一個對象進過可達性分析後發現其與"GC Roots"不可達,此時便會對該對象進行第一次標記,再次通過篩選後才能最終斷定該對象是否須要回收,篩選的條件是此對象是否有必要執行finalize()方法。當對象沒有覆蓋finalize()或者finalize()方法已經被虛擬機調用過期該對象將被最終肯定爲待回收對象(虛擬機將這兩種狀況視爲"沒有必要執行");
若是這個對象被斷定爲有必要執行 finalize()方法。那麼這個對象將會被放置在一個叫作F-Queue隊列中,並在稍後由一個虛擬機自動創建的、低優先級的Finalizer線程去執行它。此線程只會去觸發方法的運行,並不保證方法運行結束(防止隊列中的其餘待執行對象長時間等待,致使整個內存回收系統崩潰),當一個對象在本身的 finalize()方法中與引用鏈上的對象創建了關聯,則本次不會回收該對象,反之則會被標記爲待回收對象。
怎樣回收?
回收算法:
a、標記-清除算法
思路:該算法分爲「標記」和「清除」兩個階段:首先標記出全部須要回收的對象,在標記完成後統一回收全部被標記的對象,它的標記過程即用上述兩種標記算法實現。
不足:(1)效率問題,標記和清除兩個過程效率都不高;
(2)空間問題,因爲是直接清除待回收對象,則會致使大量內存碎片,空間碎片太多可能進而致使之後在程序運行過程當中須要分配較大對象時,沒法找到足夠的連續內存而不得不提早觸發另外一次垃圾收集動做。
b、複製算法
爲了解決效率問題,複製(coping)算法油然而生,概念模型上它將可用的內存分爲大小相等的兩塊,每次只使用其中的一塊,當這一塊的內存用完了,就將還存活的對象複製到另一塊上面,而後再把已使用過的內存空間一次清理掉。這樣使得每次都是對整個半區內存進行回收,內存分配時也就不用考慮內存碎片的問題,直接移動堆頂指針,按順序分配便可,實現簡單,運行高效。
不足點也很明顯,內存代價太大。
目前商用虛擬機基本都採用這種收集算法來回收新生代。實際上在新生代中也不是按照1:1的比例來劃份內存空間,而是講內存分爲一塊較大的Eden空間和兩塊Survivor空間,每次使用Eden和其中一塊Survivor。當回收時,將Eden和Survivor中還存活的對象一次性地複製到另一塊Survivor空間上,最後清理掉Eden和剛纔用過的Survivor空間。
須要注意爲啥劃分出兩個Survivor,主要有兩個緣由:一、做爲新生代與老年代的緩衝;二、減小空間碎片。另外還要注意的是,在複製的過程當中Survivor的內存空間不夠用怎麼辦?此時老年代會站出來作內存擔保。
c、標記-整理算法
複製收集算法在對象存活率較高時就要進行較多的複製操做,效率將會變低。更爲關鍵的是,若是不想浪費50%的空間,就須要有額外的空間進行分配擔保。根據老年代的特色(對象存活率較高),有人提出標記-整理算法,標記的含義及理解與前文一致,整理的思想是:讓全部存活的對象都向一端移動,而後直接清除掉邊界之外的內存。
d、分代收集算法
其實也就是根據新生代老年代的特性分別選擇適合的收集算法,新生代因爲一半存活對象少選擇使用複雜算法進行收集,老年代則使用整理算法進行收集。
什麼時候回收?
通常的斷定條件爲程序具備長時間執行的特性,如方法調用、異常跳轉、循環跳轉等。
與君共勉
文章主要思想來源於書籍:《深刻理解Java虛擬機》