垃圾收集器與內存分配策略

哪些內存需要回收?
什麼時候回收?
如何回收?
運行時數據區的程序計數器、虛擬機棧、本地方法棧3個區域是線程私有的,與線程生命週期一致;棧中的棧幀隨着方法的進入和退出而有條不紊地執行出棧和入棧操作。每一個棧幀中分配多少內存基本上是在結構確定時就已知,因此這幾個區域的內存分配和回收都具備確定性,在這幾個區域內就不需要過多考慮回收的問題,因爲方法結束或者線程結束時,內存自然就隨着回收了。而Java堆和方法區則不一樣,一個接口中的多個實現類需要的內存可能不一樣,一個方法中的多個分支需要的內存也不一樣,只有程序處於運行期間才能知道會創建哪些對象,這部分內存的分配和回收都是動態的,垃圾收集器所關注的是這部分內存。
判斷對象是否存活的算法:
引用計數法:給對象中添加一個引用計數器,每當有一個地方引用它時,計數器值加1,當引用失效時,計數器值減1,任何時候計數器爲0的對象就是不可能再被使用的。
主流Java虛擬機沒有選用引用計數算法來管理內存,其中最主要的原因是她很難解決對象之間相互循環引用的問題。
可達性分析算法:通過一系列的稱爲“GC Roots”的對象作爲起始點,從這些節點開始向下搜索,搜索所走過的路徑稱爲引用鏈,當一個對象到GC Roots沒有任何引用鏈相連,就是GC roots到這個對象不可達,則證明此對象是不可用的。
在Java語言中,可作爲GC Roots的對象包含以下幾種:

  • 虛擬機棧(棧幀中的本地變量表)中引用的對象
  • 方法區中類靜態屬性引用的對象
  • 方法區中常量引用的對象
  • 本地方法棧中JNI(即一般說的Native方法)引用的對象

JDK1.2以前Java中定義的引用:如果reference類型的數據中存儲的數值代表的是另一塊內存的起始地址,就稱這塊內存代表着一個引用。
JDK1.2之後,將引用分爲強引用、軟引用、弱引用、虛引用4種,4種引用強度一次逐漸減弱。

  • 強引用:就是指在程序代碼之中普遍存在的,類似Object obj=new Object()這類的引用,只要強引用還存在,垃圾收集器永遠不會回收掉被引用的對象。
  • 軟引用:用來描述一些還有用但並非必需的對象。對於軟應用關聯着的對象,在系統將要發生內存溢出異常之前,將會把這些對象列進回收範圍之中進行第二次回收。如果這次回收還沒有足夠的內存,纔會拋出內存溢出異常。在JDK1.2之後,提供了SoftReference類來實現軟引用。
  • 弱引用:也是用來描述非必需對象的,但它的強度比軟引用更弱一些,被弱引用關聯的對象只能生存到下一次垃圾收集發生之前。當垃圾收集器工作時,無論當前內存是否足夠,都會回收掉只被弱引用關聯的對象。WeakReference類實現弱引用。
  • 虛引用:也稱幽靈引用或幻影引用,它是最弱的一種引用關係。一個對象是否有虛引用的存在,完全不會對其生存空間構成影響,也無法通過一個虛引用來取得一個對象實例。爲一個對象設置虛引用關聯的唯一目的就是能在這個對象被收集器回收時收到一個系統通知。PhantomReference類來實現虛引用。
    對象死亡:
    對象回收流程
    回收方法區:永久代的垃圾回收主要回收兩部分內容:
  • 廢棄常量
  • 無用的類
    以常量池中字面量的回收爲例,如果一個字符串“abc”已經進入了常量池中,當前系統沒有任何一個String對象是叫做“abc”的,即沒有任何String對象引用常量池中的“abc”常量,也沒有其他地方引用了這個字面量,如果這時發生內存回收,而且必要的話,這個“abc”常量就會被系統清理出常量池。常量池中的其他類(接口)、方法、字段的符號引用也與此類似。
    無用的類:
  • 該類的所有實例都已經被回收
  • 加載該類的ClassLoader已經被回收
  • 該類對應的java.lang.Class對象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法
    虛擬機可以對同時滿足以上三個條件的無用類進行回收
    垃圾收集算法:
    標記-清除算法:
    首先標記出所有需要的對象,在標記完成後統一回收所有被標記的對象
    這裏寫圖片描述

缺點:效率問題,標記和清除的兩個過程的效率都不高;空間問題,標記清除之後會產生大量不連續的內存碎片,空間碎片太多可能會導致以後在程序運行過程中需要分配較大對象時,無法找到足夠的連續內存而不得不提前觸發另一次垃圾收集動作。
複製算法:
將可用內存按容量劃分爲大小相等的兩塊,每次只使用其中的一塊。當這一塊內存用完了,就將還存活的對象複製到另一塊上面,然後再把已使用過的內存空間一次清理掉。這樣使得每次是對整個半區進行內存回收,內存分配時也就不用考慮內存碎片等複雜情況,只要移動堆頂指針,按順序分配內存即可,實現簡單,運行高效。
缺點:減少了一半可用內存
這裏寫圖片描述

標記-整理算法:
複製收集算法在對象存活率較高時就要進行較多的複製操作,效率將會變低。更關鍵的是,如果不想浪費50%的空間,就需要有額外的空間進行分配擔保,以應對被使用的內存中所有對象都100%存活的極端情況,所以在老年代一般不能直接選用這種算法。
標記的過程仍與“標記-清除”算法一樣,但後續步驟不是直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動,然後直接清理掉邊界以外的內存。
這裏寫圖片描述
分代收集算法:
當前商業虛擬機的垃圾收集都採用“分代收集”算法,這種算法並沒有什麼新的思想,只是根據對象存活週期的不同將內存劃分爲幾塊。一般是把Java堆分爲新生代和老年代,這樣就可以根據各個年代的特點採用最適當的收集算法。在新生代中,每次垃圾收集時都發現有大批對象死去,只有少量存活,那就選用複製算法,只需要付出少量存活對象的複製成本就可以完成收集。而在老年代中因爲對象存活率高、沒有額外空間對它進行分配擔保,就必須使用“標記-清理”或者“標記-整理”算法來進行回收。

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