《深入理解JVM》10-垃圾回收

David_lou發表於2020-10-15

本系列的第七篇寫過一次垃圾回收,今天看起來優點髒亂差,貽笑大方。這節課是對鄭雨迪老師的課程總結,希望對大家有點幫助。

第五節的時候我們已經總結過JVM的記憶體設計了

就是上面這張圖,相比大家對這個圖已經熟記於心了,我們知道新生成的物件都會被分配到堆這塊區域,但是隨著系統的不斷執行,堆內的物件越來越多,這個時候就要清理一下,回收無用的物件的記憶體空間,分配給新建立的物件。這就是垃圾回收機制,Java語言能夠如此的風靡有很大一部分原因是JVM自帶了優秀的垃圾回收器,讓開發人員不用關注記憶體空間分配的問題。

死亡物件辨別方法

  1. 引用計數器演算法
  2. 可達性分析演算法

最開始的時候,JVM為每個物件提供一個引用計數器,計算指向該物件的引用次數,如果此計數器不為0,表明有使用該物件的其他物件,那麼該物件就是存活的。缺點:記憶體負荷,迴圈依賴,如下圖

為了破解迴圈依賴的問題,jvm優化了演算法,採用可達性分析演算法判斷物件的存活。演算法思想:

  1. 找到虛擬機器內一組正在被使用的物件,成為GC root
  2. 從GC root 開始遍歷引用路徑,只要能隨著引用路徑到達的物件,就說明物件存活。

如何確定GC root

  1. 當前java方法棧棧幀的區域性變數
  2. 被載入到方法區的類的靜態變數
  3. JNI handles
  4. 已啟動未終止的執行緒

缺點,在併發的情況下,可能會誤遍歷不能再到達的物件,比如A執行緒剛執行完,引用的X物件不會被使用了,但是掃描已經標記該物件為可達的。為了確保正確性,JVM引入了Stop The world 和安全點的概念

Stop the world

stop the world 顧名思義,就是要暫停JVM中所有非垃圾回收的工作,直到垃圾回收完成,這個stop the world 會引起服務暫停,也叫GC pause。記憶體分配執行緒發現記憶體不夠用時,會申請JVM進行垃圾回收,提出stop the world請求,虛擬機器會等所有的執行緒進入“安全點”,才允許stop the world進入執行緒獨佔工作。

安全點

安全點不是真正的點,也不是讓執行緒暫停,而是進入一種穩定的狀態,讓GC root不再變動,即JVM的java虛擬機器棧幀不再變動,JNI handles不再變動,只要java方法棧的棧幀不再變動,執行緒還是可以呼叫當前棧幀中的程式碼,即不出現java方法棧入棧出棧操作。執行緒有幾種狀態:

  1. 解釋執行器執行狀態,逐條解釋執行,可以每條指令檢查安全點
  2. 即時編譯器執行狀態,只有在方法回邊的時候檢查安全點
  3. 阻塞狀態:因為java執行緒呼叫在JVM管制範圍內,所以安全

垃圾回收的三種方式

清除:把死亡的物件刪除

壓縮:把死亡的物件刪除,並且移動存活的物件到記憶體一側

複製:把記憶體分為兩部分,每次只用一份,記憶體不夠時,把使用部分的或物件複製到另外一部分記憶體,並清理回收當前記憶體空間

分代思想

物件存活的時間:

根據統計,jvm中的物件按照2-8定律,80%的物件存活的時間極短,只有20%的物件存活的時間很長,所以,80%的物件需要頻繁GC,另外的20%不要,那麼把這兩部分物件分開GC可以獲得更好的GC效果,這就是分代思想。

年輕代:新建立的物件,存放在年輕代

老年代:經歷過預設15次年輕代GC的物件,存放在老年代

年輕代的GC使用的複製方法,把年輕代分為3部分,Eden/Survivor/Survivor三個部分,空間大小8:1:1。

存活的物件被複制到Survivor1區,GC完之後,Survivor1的物件複製到2中,回收Eden和Survivor1,下次GC繼續這麼操作。Survivor區的物件經歷過15次這樣的GC之後,會被移動到老年代。

老年代如果引用了年輕代的物件,在Minor GC的時候,豈不是要遍歷所有的老年代,這不意味著要全堆掃描,為了省略遍歷老年代的時間,引入卡表的概念

卡表:

把對記憶體分為大小相同的區域,每個區域新建一塊卡片,儲存物件引用資訊,那麼就意味著要在所有的寫操作寫入該卡片,JVM引入寫入 屏障 write barrier,這個屏障寫卡片資訊,非常的快。

新生代的垃圾回收器

  1. Serial :單執行緒的標記-複製
  2. Paralle Scavenge:跟Parallel差不多,但是更注重吞吐量。不能與CMS共同使用
  3. Parallel New 多執行緒的標記-複製

老年代垃圾回收器

  1. Serial Old:單線的標記壓縮
  2. Parallel Old:多執行緒的標記壓縮
  3. CMS:標記刪除,隨著G1的發展,CMS在java9之後廢棄

G1回收器

已經打亂了分代的思想,把堆記憶體劃分為多個記憶體區域,每個區域維護一個記憶體分配情況表,當記憶體不夠時,有限回收記憶體中垃圾佔比較大的空間。所以叫Garbage First

 

 

 

相關文章