天天看點

深入了解Java虛拟機(二)垃圾收集算法

JVM讨論的需要垃圾回收的區域主要是指堆記憶體和方法區。

因為:程式計數器、虛拟機棧、本地方法棧3個區域随線程而生,随線程而滅;棧中的棧幀随着方法的進入和退出而有條不紊地執行着出棧和入棧操作。每一個棧幀中配置設定多少記憶體基本上是在類結構确定下來時就已知。是以這幾個區域的記憶體配置設定和回收都具備确定性,在這幾個區域内就不需要過多考慮回收的問題,因為方法結束或者線程結束時,記憶體自然就跟随着回收了。

考慮下面三個問題:

  • 哪些記憶體需要回收?
  • 什麼時候回收?
  • 怎麼回收?

1.判斷對象生死(用于堆記憶體回收)

堆記憶體中存放的是所有對象的執行個體,是以垃圾收集器在回收前做的第一件事就是确定這些對象哪些還活着,哪些已經死去(沒有被任何途徑使用,可被回收)。

  • 引用計數器算法:給對象中添加一個引用計數器,每當有一個地方引用它,計數器就加1;當引用失敗時,引用計數器就減1。當引用計數器為0時,對象就是不可能被使用的。(這種算法是不對的,因為它無法解決對象之間互相循環引用的問題)
  • 可達性分析算法:通過一系列“GC Roots”對象作為起始點,從這些節點開始向下搜尋,搜尋走過的路稱為引用鍊(Reference Chain),當一個對象到GC Roots沒有任何引用相連。則證明此對象是不可用的。“GC Roots”對象來自于以下幾個方面。
    • 虛拟機棧(棧幀中的本地變量表)中引用的對象。
    • 方法區中靜态屬性引用的對象。
    • 方法區中常量引用的對象。
    • 本地方法棧中JNI(Native方法)引用的對象。

2.可能導緻對象複活的finalize方法

經過可達性分析算法判斷“對象不可達”以後,對象不一定就死,如果對象實作了finalize方法,且是第一次調用finalize方法,這時虛拟機将會先執行finalize方法,如果該方法中将自己(this關鍵字)指派給某個靜态變量(也稱為類變量)或者對象的成員變量,這種情況該對象就不會被回收,也可被稱為複活了。

  • finalize方法隻會被自動調用一次。
  • finalize不一定會全部執行完畢。
  • finalize方法優先級很低。
如果這個對象被判定為有必要執行finalize()方法,那麼這個對象将會放置在一個叫做F-Queue的隊列之中,并在稍後會有一個由虛拟機自動建立的、優先級的Finalizer線程去執行它。但是虛拟機并不承諾會等待它運作結束,因為如果一個對象在finalize方法中執行緩慢,或者發生了死循環,将會導緻F-Queue隊列中其他對象永久處于等待,甚至整個記憶體回收系統崩潰。

3.方法區回收

此部分主要回收兩部分:

  • 廢棄常量
  • 無用的類
  • 廢棄常量:加入字元串常量池裡面的一個字元串“abc”,在目前沒有任何一個String對象是叫做“abc”的,也就是沒有任何String對象引用“abc”,這樣的常量就是廢棄常量。
  • 無用的類需要同時滿足一下三點:
    • 該類所有的執行個體都被回收,Java堆記憶體中不存在任何該類的任何執行個體。
    • 加載該類的ClassLoader已經被回收。
    • 該類對應的java.long.Class對象沒有任何地方被引用,無法在任何地方通過反射通路該類的方法。

廢棄常量一定會被回收,但是滿足上述3個條件的無用類進行回收,這裡說的僅僅是“可以”,而并不是和對象一樣,不使用了就必然會回收。HotSpot虛拟機提供了 -Xnoclassgc參數進行檢測class的加載和解除安裝。

//檢視類加載資訊和類解除安裝資訊的三個參數:
-verbose:class 
-XX:+TraceClassLoading //需要Product版的虛拟機中使用
-XX:+TraceClassUnLoading //需要FastDebug版虛拟機支援
           

4.垃圾收集算法

  • 标記-清除算法
  • 複制算法
  • 标記-整理算法

标記-清除算法:标記出所有需要清理的記憶體,編輯完成後統一進行清理。

缺點:

有可能将記憶體碎片化,導緻記憶體不那麼連續,如果下一次遇到大對象需要配置設定連續的記憶體空間,将提前觸發下一次垃圾回收。是以效率較低。

深入了解Java虛拟機(二)垃圾收集算法

複制算法:它将可用記憶體按容量劃分為大小相等的兩塊,每次隻使用其中的一塊。當這一塊的記憶體用完了,就将還存活着的對象複制到另外一塊上面,然後再把已使用過的記憶體空間一次清理掉。

缺點:

記憶體縮小為了原來的一半。

複制收集算法在對象存活率較高時就要進行較多的複制操作,效率将會變低。更關鍵的是,如果不想浪費50%的空間,就需要有額外的空間進行配置設定擔保,以應對被使用的記憶體中所有對象都100%存活的極端情況,是以在老年代一般不能直接選用這種算法。
深入了解Java虛拟機(二)垃圾收集算法

标記-整理算法:标記的過程與标記-清理的過程一樣,标記後以不是直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動,然後直接清理掉端邊界以外的記憶體

深入了解Java虛拟機(二)垃圾收集算法
非常重要:
目前商業虛拟機的垃圾收集都采用“分代收集”(Generational Collection)算法,這種算法并沒有什麼新的思想,隻是根據對象存活周期的不同将記憶體劃分為幾塊。一般是把Java堆分為新生代和老年代,這樣就可以根據各個年代的特點采用最适當的收集算法。在新生代中,每次垃圾收集時都發現有大批對象死去,隻有少量存活,那就選用複制算法,隻需要付出少量存活對象的複制成本就可以完成收集。而老年代中因為對象存活率高、沒有額外空間對它進行配置設定擔保,就必須使用“标記—清理”或者“标記—整理”算法來進行回收。