經過前面是學習我們已經對java運作時區域的各個部分有了一定的了解,其中程式計數器,虛拟機棧,本地方法棧3個區域屬于線程私有區域,它們随着線程的建立而存在,随着線程的死亡而釋放。棧中的棧幀随着方法的進入和退出也在有條不紊的進行着入棧和出棧操作。每一個棧幀中配置設定多少記憶體基本上是在類結構确定下來時就已知的,是以上述區域的記憶體配置設定和回收都是确定的,一旦方法結束或者線程結束,相應的記憶體自然也就被回收了。而java堆和方法區則不一樣,我們隻有在程式處于運作期間時才會知道哪些對象會被建立,這部分記憶體的配置設定和回收都是動态的,具有不确定性,而垃圾收集器所要管理的也就是這部分記憶體。
如何判斷哪些對象需要被回收?
java堆中存放着幾乎所有的對象執行個體,垃圾回收器在對java堆進行回收前,需要知道哪些對象還活着,哪些對象已經死了(即不可能再被引用的對象)。
判斷對象是否存活有兩種方法:引用計數法和可達性分析法
1. 引用計數法
給每個對象中添加一個引用計數器,每當有一個地方引用它時,計數器值就加1;當引用失效時,計數器就減1;任何時刻計數器為0的對象就是不可能再被使用的對象,此時該對象為可回收對象。
客觀的說,引用計數法實作簡單,判定效率也很高,但是其存在一個緻命的弱點:無法解決對象之間循環引用的問題。如對象A和對象B都有字段instance,令A.instance = B;B.instance=A;除此之外,兩個對象再無任何引用,現令A=null;B=null;實際上這兩個對象已經不再可能被引用,但是因為它們之間互相引用,導緻它們的計數器值都不為0,是以GC無法回收它們。
2. 可達性分析法
該算法的基本思想是通過一系列的成為“GC roots”的對象作為起始點,從這些節點開始向下搜尋,搜尋所走過的路徑稱為引用鍊(reference chain),當一個對象到GC roots沒有任何引用鍊相連時,則證明此對象是不可用的。在java語言中,可作為GC roots的對象包括以下幾種:
1)虛拟機棧(棧幀中的本地變量表)中引用的對象;
2)方法區中類靜态屬性引用的對象;
3)方法區中常量引用的對象;
4)本地方法棧中JNI(即native方法)引用的對象。
注意:
1.即使在可達性分析算法中不可達的對象,也并非是“非死不可”的,這時它處于“緩刑”階段,要正真宣告一個對象“死亡”,至少要經曆兩次标記過程。如果對象在經曆可達性分析後發現沒有與GC roots的引用鍊,那它将會被第一次标記并進行一次篩選,篩選的條件是此對象是否有必要執行finalize()方法,當對象沒有覆寫finalize()方法或者finalize()方法已經被虛拟機調用過,則虛拟機将這兩種情況視為“沒有必要執行”。
如果該對象被判定為有必要執行finalize()方法,那麼這個對象将會被放到一個叫做F-Queue的隊列中,并在稍後在一個由虛拟機自動建立的低優先級的Finalizer線程負責運作,但是虛拟機并不“承諾”會等待它執行結束。finalize()方法是對象逃脫死亡命運的最後一次機會。
2.很多人認為方法區(或者HotSpot虛拟機中的永久代)是沒有垃圾收集的,java虛拟機規範中确實說過可以不要求虛拟機在方法區實作垃圾收集。但是,GC仍然會對方法區進行垃圾收集,收集的内容包括無用的類和廢棄常量。
垃圾收集算法
1.标記清除算法
最基礎的算法是标記-清除算法,該算法分為“标記”和“清除”兩個階段:首先标記處所有需要回收的對象,在标記完成後統一回收所有被标記的對象。
該方法的缺點:1)效率低下,标記和清除兩個過程效率都相對較低;2)标記清除之後會産生大量不連續的記憶體碎片,記憶體碎片太多可能會導緻以後在程式需要建立較大對象時,無法找到足夠的連續記憶體而不得不提前觸發另一次垃圾收集動作。
2.複制算法
該算法将可用記憶體按容量劃分為大小相等的兩塊,每次隻是用其中的一塊,當這一塊的記憶體用完了,就将還存活的對象複制到另一塊上面,然後把用過的記憶體空間一次性清理掉。
優缺點:優點:每次都是對整個記憶體半區進行垃圾回收,記憶體配置設定時也就不用考慮記憶體碎片等複雜情況,隻要移動堆頂指針,按順序配置設定記憶體即可,實作簡單,運作高效;缺點:記憶體使用率低,這種算法的代價是将記憶體縮小為了原來的一半,代價太大。
3.标記-整理算法
标記整理算法,标記過程與标記清除算法一樣,但後續步驟不是直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動,然後直接清理掉邊界以外的記憶體。
4.分代收集算法
目前商業虛拟機的垃圾收集都采用“分代收集”算法,該算法根據對象存活周期的不同,将記憶體劃分為幾塊。一般把java堆劃分為新生代和老年代,這樣就可以根據各個年代的特點采用最适當的算法。在新生代中,每次垃圾收集都發現有大批對象死去,隻有少量存活,那就采用複制法,隻需要付出少量存活對象的複制成本就可以完成收集。而老年代中對象存活率高,沒有額外空間對它進行配置設定擔保,就必須采用“标記-清除”算法或“标記-整理”算法來進行回收。
總結:
判斷對象是否存活的方法:引用計數法(效率高,但是無法解決循環引用問題)和可達性分析法
垃圾收集算法:标記清除法(效率低,記憶體碎片多),複制算法(記憶體使用率低,代價大),标記整理算法(适合老年代垃圾收集),分代回收算法