深入理解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.垃圾收集算法

  • 標記-清除算法
  • 複製算法
  • 標記-整理算法

標記-清除算法:標記出所有需要清理的內存,編輯完成後統一進行清理。缺點:有可能將內存碎片化,導致內存不那麼連續,如果下一次遇到大對象需要分配連續的內存空間,將提前觸發下一次垃圾回收。所以效率較低。
這裏寫圖片描述

複製算法:它將可用內存按容量劃分爲大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活着的對象複製到另外一塊上面,然後再把已使用過的內存空間一次清理掉。缺點:內存縮小爲了原來的一半。

複製收集算法在對象存活率較高時就要進行較多的複製操作,效率將會變低。更關鍵的是,如果不想浪費50%的空間,就需要有額外的空間進行分配擔保,以應對被使用的內存中所有對象都100%存活的極端情況,所以在老年代一般不能直接選用這種算法。

這裏寫圖片描述

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

這裏寫圖片描述

非常重要:

當前商業虛擬機的垃圾收集都採用“分代收集”(Generational Collection)算法,這種算法並沒有什麼新的思想,只是根據對象存活週期的不同將內存劃分爲幾塊。一般是把Java堆分爲新生代和老年代,這樣就可以根據各個年代的特點採用最適當的收集算法。在新生代中,每次垃圾收集時都發現有大批對象死去,只有少量存活,那就選用複製算法,只需要付出少量存活對象的複製成本就可以完成收集。而老年代中因爲對象存活率高、沒有額外空間對它進行分配擔保,就必須使用“標記—清理”或者“標記—整理”算法來進行回收。

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