Java垃圾回收機制

哪些內存需要回收?

由於程序計數器、虛擬機棧、本地方法棧是線程私有的,即隨着線程生而生,線程死而死;而java堆和方法區是線程共享的,所以垃圾回收GC面向java堆(主要)和方法區。

垃圾是什麼?

一個對象,使用對象存活判定算法判定,發現不是存活狀態,即爲可回收的垃圾。

對象存活判定算法:

1. 引用計數算法:

  • 給對象添加一個引用計數器,每當有一個地方引用它時,計數器加1;當一個引用失效時,計數器減1。對一個對象,其引用計數爲0,則未被引用,即判定爲可回收的垃圾。
  • 優點:算法簡單,效率高
  • 缺點:不能解決對象之間相互循環引用的問題
    • eg:兩個對象,除了互相引用之外,再無任何其他的引用,實際上這兩個對象已經不可能被訪問了,但是通過引用計數算法不能判斷爲可回收的垃圾。

2. 可達性分析算法(目前一般都用這種):

  • 從一個“GC Roots”出發,向下搜索,到某一個對象節點沒有任何路徑可達,則稱“GC Roots”到這個對象不可達,即該對象不可用,可以判定爲可回收的垃圾。
    • 可作爲 GC Roots的對象包括:
      • 虛擬機棧中引用的對象
      • 方法區中靜態屬性引用的對象
      • 方法區中常量引用的對象
      • 本地方法棧中Native方法引用的對象
  • 優點:解決了引用計數算法不能解決的循環引用的問題

引用(插入講解)

引用分爲4種類型,從強到弱依次是:
強引用(Strong Reference) > 軟引用(Soft Reference) > 弱引用(Weak Reference)> 虛引用(Phantom Reference)

  • 強引用:代碼中普遍存在的引用形式,只要強引用存在,就不能GC回收
  • 軟引用:描述一些還有用但非必需的對象,當內存不足,即將發生OOM(Out Of Memory)異常時,會加入GC回收範圍,下次進行回收
  • 弱引用:同樣是描述非必需的對象,但比軟引用強度弱,即不管內存是否充足,下次進行GC時,會被回收
  • 虛引用(幽林引用/幻影引用):能在這個對象被收集器回收時受到一個系統通知

垃圾回收的方法?

垃圾收集算法,大致分爲以下4種:標記-清除算法、複製算法、標記-整理算法、分代收集算法,其中標記-清除算法是最基礎的收集算法,後面的算法都是在其基礎上不斷進行改進而產生的。
1. 標記-清除算法(Mark-Sweep):

  • 分爲兩個過程:標記和清除,先標記出所有需要回收的對象,標記完成後,再統一回收所有被標記的對象。
  • 缺點:
    • 標記和清除的效率都不高;
    • 會形成大量不連續的內存碎片,導致後面對較大對象的分配出現內存不足,而提前觸發另一次GC。

2. 複製算法(Copying):

  • 將可用內存一分爲二,每次只使用其中的一塊;當一塊用完了,就將還存活的對象複製到另一塊上面,再將前一塊中使用過的內存空間一次性清理掉。
  • 解決了效率和內存碎片的問題
  • 缺點:將內存縮小爲原來的一半
  • 一般用於新生代

3.標記-整理算法(Mark-Compact):

  • 分爲兩個步驟,標記和整理,標記和標記-清除算法中類似;至於整理是指:將所有存活對象都向一端移動,然後直接清理掉端邊界以外的內存。
  • 一般用於老年代

4.分代收集算法:

  • 將Java堆分爲新生代和老年代
    • 新生代:對象大量死亡,少量存活,選用複製算法
      • 新生代中將內存分爲3塊:一塊較大的Eden、兩塊較小的Survivor,每次使用Eden和一塊Survivor
      • 當回收時,將Eden和Survivor中的存活對象複製到另一塊Survivor中,再清理掉Eden和第一塊Survivor空間
      • HotSpot虛擬機默認Eden和Survivor的大小比例是8:1,即每次新生代可用內存空間爲整個新生代容量的90%
    • 老年代:對象存活率高,選用標記-清理算法或標記-整理算法

內存分配和回收策略

  • 對象優先在Eden分配
  • 大對象直接進入老年代
  • 長期存活的對象將進入老年代
  • 動態對象進行年齡判定
    • 如果Survivor中相同年齡所有對象大小的總和大於Survivor空間的一半,則年齡大於或等於該年齡的對象就可以直接進入老年代
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章