JVM(六)——GC 算法

標記-清除法


JVM(六)——GC 算法

概述

  GC 是 JVM 自帶的功能,它可以自動回收對象,清理內存,這是 Java 語言的一大優點,可是GC毫不僅伴隨着Java,相反,GC歷史比Java更悠久。關於GC,我認爲有四個問題須要解決:java

  • 爲何瞭解 GC?
  • 哪些內存須要回收?
  • 何時回收?
  • 如何回收?

爲何瞭解 GC

  GC 已經比較成熟,絕大部分狀況下都「自動化」運行。之因此還須要瞭解GC,是由於當須要排查各類內存溢出、內存泄露問題時,當垃圾收集成爲系統達到更高併發量的瓶頸時,咱們就須要對這些「自動化」的技術實施必要的監控和調節。c++

哪些內存須要回收

  斷定哪些內存須要回收,不是靠 JVM 去猜,也不是隨機,而是 JVM 靠一系列算法獲得結果,固然,算法也是人寫的,雖然不能作到百分之百地符合全部開發者的要求,但這已是最好的了。下面介紹一種斷定內存回收的算法——可達性分析算法,這是我暫時能理解的算法。程序員

可達性分析算法


  這個算法的基本思路是經過一系列的稱爲 「GC Roots」 的對象做爲起點,從這些起點開始向下搜索,搜索走過的路徑稱爲引用鏈,當一個對象到 GC Roots 沒有任何引用鏈相連時,則證實此對象是不可用的,也就是待回收的。例如,上圖中的 Object 五、Object 6 雖然二者相互關聯,可是它們任何一個都和 GC Roots 不可達,因此 Object 5 和 Object 6 將會被斷定爲可回收的對象。

GC Roots

  至於什麼是 GC Roots 對象,解釋以下:算法

  • 虛擬機棧(棧幀中的本地變量表)引用的對象
  • 方法區中類靜態變量引用的對象
  • 方法區中常量引用的對象
  • 本地方法棧 JNI 引用的變量

何時回收

  當經過「可達性分析」等算法標記好須要回收的對象後,等待它們的不是便可問斬,而是宣告「緩刑」。最後的回收條件是此對象是否有必要執行 finalize() 方法。併發

finalize() 方法

  當對象沒有覆蓋 finalize() 方法,或者 finalize() 方法已經被虛擬機調用過,虛擬機都將視做「沒有必要執行」。若是這個對象唄斷定爲有必要執行 finalize() 方法,那麼這個對象將會放置在一個叫做 F-Queue 的隊列中,這個隊列在後期會被某個JVM自動建立的線程執行,若是一個對象在finalize()方法中執行緩慢或者發生了死循環,將可能致使 F-Queue 隊列中其餘對象永久處於等待狀態,甚至致使整個內存回收系統崩潰,幸運的是任何一個對象的 finalize() 方法只會被系統調用一次。
  在 《深刻理解 JVM 虛擬機》中,做者建議儘可能避免使用 finalize() 方法,它不是c++ 中的析構函數,更像是java爲適應c++程序員而做出的讓步。我以爲是finalize()方法是一根雞肋,一根若是運用不當將會引起災難的雞肋,如同 goto 語句。它的運行代價極高,不肯定性大,沒法保證各個對象的調用順序。它的功能常常會被誤覺得相似 try-finally,然而 finalize() 能作的工做, try-finally 都能作,並且作得更好、更及時。   函數

如何回收

標記-清除法

  「標記-清除」(Mark-Sweep)法是最基礎的收集算法。算法分爲兩個階段——「標記」和「清除」:首先標記出全部須要被回收的對象,在標記完成後統一回收全部標記了的對象,圖如篇首。
  它有兩個不足之處:高併發

  • 效率不高
      標記和清除兩個過程的效率都不高
  • 產生大量不連續碎片
      雖然對象被回收,可是剩下的內存頗有可能不連續,在這種狀況下,當須要爲系統分配一個較大對象時,會由於沒法找到足夠的連續的內存而不得不提早觸發另外一次垃圾收集動做。這樣,又會形成效率問題

  針對上述兩個缺點,後面兩種算法對此進行了改進。線程

複製法

  「複製」(Copying)是爲了解決「標記-清除」的效率不高問題而被髮明。它將內存按容量劃分爲等量的兩塊。每次只使用其中的一塊。當使用的這一塊內存用完了,就將還存活的對象複製到另外一塊(被稱做「保留區」)上面,而後再把已使用的內存空間一次清理掉。每次都對整個半區進行回收,不再用擔憂內存碎片化問題,圖以下所示。3d

複製法

  可是「複製」法也有缺點,每次都將內存縮小爲一半,會致使內存利用率不高。而且在對象存活率高時進行復制操做,效率就會變低(由於存活的對象要複製到「保留區」)。當趕上極端狀況,對象存活率百分百(以原內存一半爲單位),那就須要另外的百分之五十的空間做爲分配擔保。cdn

標記-整理法

  「標記-整理」(Mark-Compact)法適合對象存活率高的狀況使用。「標記」過程同「標記-清除」法同樣,但後續步驟不是直接對可回收對象進行清理,而是讓全部存活的對象都向一端移動,而後直接清理掉端邊界之外的內存,圖以下所示:

標記-整理法

分代收集法

  據知,當前商業虛擬機都採用「分代收集」(Generational Collection)法,根據對象的存活週期的不一樣將內存劃分爲幾塊。通常是把 Java 堆分紅新生代和老年代,這樣能夠根據更年代的特色採用最適當的收集算法。

新生代

  在新生代中,每次垃圾收集時都會發現有大量對象死去,只有少許存活,那就選用複製算法,只須要複製少許的對象就能夠完成收集,成本小。

老年代

  老年代中的對象存活率高、沒有額外的空間對它進行分配擔保,就必須使用「標記-清除」、和「標記-整理」法來進行回收。

相關文章
相關標籤/搜索