目錄
一、判斷對象已“死”
1.1、引用計數算法
1.2、可達性分析算法
1.3、引用的概念
二、垃圾收集算法理論
2.1、分代收集理論
三、垃圾收集算法
3.1、標記--清除算法
?3.2、標記--復制算法
3.3、標記--整理算法
一、判斷對象已“死”
????????在堆里面存放著Java世界中幾乎所有的對象實例,垃圾收集器在對堆進行回收前,第一件事情就是要確定這些對象之中哪些還“存活”著,哪些已經“死去”(“死去”即不可能再被任何途徑使用的對象)了。
1.1、引用計數算法
????????在對象中添加一個引用計數器,每當有一個地方引用它時,計數器值就加一;當引用失效時,計數器值就減一;任何時刻計數器為零的對象就是不可能再被使用的。但是主流的Java虛擬機里面都沒有選用引用計數算法來管理內存。
優點:實現簡單,效率高。
缺點:很難解決對象之間循環引用的問題。
1.2、可達性分析算法
????????當前主流的商用程序語言(Java、C#,上溯至前面提到的古老的Lisp)的內存管理子系統,都是通過可達性分析算法來判定對象是否存活的。這個算法的基本思路就是通過一系列稱為“GC Roots”的根對象作為起始節點集,從這些節點開始,根據引用關系向下搜索,搜索過程所走過的路徑稱為“引用鏈”(Reference Chain),如果某個對象到GC Roots間沒有任何引用鏈相連,或者用圖論的話來說就是從GC Roots到這個對象不可達時,則證明此對象是不可能再被使用的。
如下圖3-1:
?固定可作為GC Roots的對象包括以下幾種:
- 在虛擬機棧(棧幀中的本地變量表)中引用的對象,譬如各個線程被調用的方法堆棧中使用到的參數、局部變量、臨時變量等。
- 在方法區中類靜態屬性引用的對象,譬如Java類的引用類型靜態變量。
- 在方法區中常量引用的對象,譬如字符串常量池(String Table)里的引用。
- 在本地方法棧中JNI(即通常所說的Native方法)引用的對象。
- Java虛擬機內部的引用,如基本數據類型對應的Class對象,一些常駐的異常對象(比如NullPointExcepiton、OutOfMemoryError)等,還有系統類加載器。
- 所有被同步鎖(synchronized關鍵字)持有的對象。
- 反映Java虛擬機內部情況的JMXBean、JVMTI中注冊的回調、本地代碼緩存等。
1.3、引用的概念
????????無論是通過引用計數算法判斷對象的引用數量,還是通過可達性分析算法判斷對象是否引用鏈可達,判定對象是否存活都和“引用”離不開關系。
????????在JDK 1.2版之后,Java對引用的概念進行了擴充,將引用分為強引用(Strongly Re-ference)、軟引用(Soft Reference)、弱引用(Weak Reference)和虛引用(Phantom Reference)4種,這4種引用強度依次逐漸減弱。
四種引用概念:
- 強引用:是最傳統的“引用”的定義,是指在程序代碼之中普遍存在的引用賦值,即類似“Objectobj=new Object()”這種引用關系。無論任何情況下,只要強引用關系還存在,垃圾收集器就永遠不會回收掉被引用的對象。
- 軟引用:用來描述一些還有用,但非必須的對象。在系統要發生oom,會回收所有的軟引用對象,如果回收完,還是放不上這個對象,才溢出。,每次垃圾回收的時候,如果對象還沒死,就不回收,但是在oom之前,不管有沒有死,都會被回收。
- 弱引用:在每次垃圾回收的時候,不管有沒有引用,都會被回收。
- 虛引用:相當于什么時候回收都沒問題,也無法通過虛引用來取得一個對象實例。 為一個對象設置虛 引用關聯的唯一目的只是為了能在這個對象被收集器回收時收到一個系統通知
二、垃圾收集算法理論
2.1、分代收集理論
????????當前商業虛擬機的垃圾收集器,大多數都遵循了“分代收集” 的理論進行設計的。
分代收集假說:
- 弱分代假說:絕大多數對象都是朝生夕滅的。
- 強分代假說:熬過越多次垃圾收集過程的對象就越難以消亡。
- 跨代引用假說:跨代引用相對于同代引用來說僅占極少數。
????????這兩個分代假說共同奠定了多款常用的垃圾收集器的一致的設計原則:收集器應該將Java堆劃分出不同的區域,然后將回收對象依據其年齡(年齡即對象熬過垃圾收集過程的次數)分配到不同的區域之中存儲。
????????把分代收集理論具體放到現在的商用Java虛擬機里,設計者一般至少會把Java堆劃分為新生代和老年代兩個區域 。顧名思義,在新生代中,每次垃圾收集時都發現有大批對象死去,而每次回收后存活的少量對象,將會逐步晉升到老年代中存放。
1、部分收集:
- 新生代收集(Minor GC/Young GC):指目標只是新生代的垃圾收集。
- 老年代收集(Major GC/Old GC):指目標只是老年代的垃圾收集。目前只有CMS收集器會有單獨收集老年代的行為。另外請注意“Major GC”這個說法現在有點混淆,在不同資料上常有不同所指,讀者需按上下文區分到底是指老年代的收集還是整堆收集。
- 混合收集(Mixed GC):指目標是收集整個新生代以及部分老年代的垃圾收集。目前只有G1收集器會有這種行為。
2、整堆收集(Full GC):收集整個Java堆和方法區的垃圾收集。
三、垃圾收集算法
3.1、標記--清除算法
1、概念:算法分為“標記”和“清除”兩個階段:首先標記出所有需要回收的對象,在標記完成后,統一回收掉所有被標記的對象,也可以反過來,標記存活的對象,統一回收所有未被標記的對象。
2、缺點:
- 執行效率不穩定:如果Java堆中包含大量對象,而且其中大部分是需要被回收的,這時必須進行大量標記和清除的動作,導致標記和清除兩個過程的執行效率都隨對象數量增長而降低;
- 內存空間的碎片化問題:標記、清除之后會產生大量不連續的內存碎片,空間碎片太多可能會導致當以后在程序運行過程中需要分配較大對象時無法找到足夠的連續內存而不得不提前觸發另一次垃圾收集動作。
?3.2、標記--復制算法
1、半區復制:將可用內存按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活著的對象復制到另外一塊上面,然后再把已使用過的內存空間一次清理掉。
優點:實現簡單,運行高效。
缺點:這種復制回收算法的代價是將可用內存縮小為了原來的一半,空間浪費太多。
?2、優化算法:
????????把新生代分為一塊較大的Eden空間和兩塊較小的Survivor空間,每次分配內存只使用Eden和其中一塊Survivor。發生垃圾搜集時,將Eden和Survivor中仍然存活的對象一次性復制到另外一塊Survivor空間上,然后直接清理掉Eden和已用過的那塊Survivor空間。HotSpot虛擬機默認Eden和Survivor的大小比例是8∶1。
3.3、標記--整理算法
????????在標記--清除算法的基礎上做了改良,用于解決空間碎片化問題。標記-整理(Mark-Compact)算法在標記后不是簡單做清除,而是讓所有存活的對象都向一端移動,然后清理掉端邊界以外的內存。一般用于老年代。
優點:解決了空間碎片化問題,為后續內存分配和訪問提高效率。
缺點:使內存回收的過程更加復雜。如果移動存活對象,尤其是在老年代這種每次回收都有大量對象存活區域,移動存活對象并更新所有引用這些對象的地方必須全程暫停用戶應用程序才能進行。
????????標記-整理算法主要用于老年代,移動存活對象是個極為負重的操作,而且這種操作需要 Stop The World 才能進行,只是從整體的吞吐量來考量,老年代使用標記-整理算法更加合適。