小師妹學JVM之:GC的垃圾回收演算法
阿新 • • 發佈:2020-06-16
[toc]
# 簡介
JVM的重要性不言而喻了,如果把java的應用程式比作一輛跑車,那麼JVM就是這輛車的發動機,沒有它,java程式就成了空中樓閣,無根浮萍。而在JVM中有一塊記憶體區域叫做執行時資料區域,儲存了執行時所需要的所有物件,而Heap Area則是其中最大的一塊。
記憶體畢竟不是無限的,所以就需要一種機制來將不再使用的物件進行回收,這種機制就是今天我們要講的GC。
更多精彩內容且看:
* [區塊鏈從入門到放棄系列教程-涵蓋密碼學,超級賬本,以太坊,Libra,比特幣等持續更新](http://www.flydean.com/blockchain/)
* [Spring Boot 2.X系列教程:七天從無到有掌握Spring Boot-持續更新](http://www.flydean.com/learn-spring-boot/)
* [Spring 5.X系列教程:滿足你對Spring5的一切想象-持續更新](http://www.flydean.com/spring5/)
* [java程式設計師從小工到專家成神之路(2020版)-持續更新中,附詳細文章教程](http://www.flydean.com/java-roadmap-2020/)
# 物件的生命週期
小師妹:F師兄,你相信這個世界有輪迴嗎?
師兄我是一個堅定的無神論者,活在當下就好了,何必操心後面的輪迴呢?
小師妹:F師兄,這個你就不懂了,意識是組成腦的原子群的一種組合模式,我們大腦的物質基礎和一塊石頭沒有什麼不同。當我們掌握大腦的組合方式,然後重構,我們的意識就重現了,這就是輪迴。這可是量子理論中提到的觀念哦。
哇,小師妹什麼時候這麼厲害了,都開始探討這麼高深的話題了。F師兄我實在是跟不上節奏啊。
小師妹,F師兄,我是怕你尷尬,想引出java物件的生命週期這個話題嘛。
量子理論我不熟,java物件我還沒怕過誰。
物件的生命週期其實很簡單:建立,使用中,最後被銷燬。
1. 建立物件
舉個最簡單的建立物件的例子:
~~~java
Object obj = new Object();
~~~
物件建立的時候,將會為該物件分配特定的空間。
2. 使用物件
物件建立之後,就可以被其他的物件使用,如果其他的物件有使用該物件,那麼我們成為該物件被引用了。
3. 物件銷燬
當一個物件沒有被其他物件引用的時候,我們就稱為該物件可以被回收了。在Java中,物件的回收是由GC來負責的。
# 垃圾回收演算法
小師妹:F師兄,我覺得垃圾回收好像挺簡單的,我們為每個物件維持一個指標計數器,每引用一次就加一,這樣不就可以實現垃圾回收器了嗎?
底層原理是這麼一個道理,但是JVM需要一種更加高效的演算法來保證垃圾回收的效率,同時也不會影響正在執行的程式。
接下來我們將會介紹一下,在JVM中比較常用幾個垃圾回收演算法:
## Mark and sweep
Mark and sweep是最最簡單的垃圾回收演算法,簡單點講,它可以分為兩個步驟:
1. 標記live物件
標記live物件聽起來很簡單,就是掃描堆中的物件,看這些物件是否被引入。
但是這裡有一個問題,如果是兩個物件互相引用的時候,而這兩個物件實際上並沒有被外部的物件所引用,那麼這兩個物件其實是應該被回收的。所以我們還需要解決一個關鍵性的問題:從哪裡開始掃描的問題。
JVM定義了一些Root物件,從這些物件開始,找出他們引用的物件,組成一個物件圖。所有在這個圖裡面的物件都是有效的物件,反之不在物件圖中的物件就應該被回收。有效的物件將會被Mark為alive。
這些Root物件包括:正在執行的方法中的本地物件和輸入引數。活動的執行緒,載入類中的static欄位和JNI引用。
> 注意,這種遍歷其實是有個缺點的,因為為了找到物件圖中哪些物件是live的,必須暫停整個應用程式,讓物件變成靜止狀態,這樣才能構建有效的物件圖。後面我們會介紹更加有效的垃圾回收演算法。
2. 刪除物件
掃描物件之後,我們就可以將未標記的物件刪除了。
刪除有三種方式,第一種方式是正常刪除。但是正常刪除會導致記憶體碎片的產生。所以第二種方式就是刪除之後進行壓縮,以減少記憶體碎片。還有一種方式叫做刪除拷貝,也就是說將alive的物件拷貝到新的記憶體區域,這樣同樣可以解決記憶體碎片的問題。
## Concurrent mark sweep (CMS)
在講CMS之前,我們先講一下垃圾回收器中的Eden,Old和Survivor space幾個大家應該都很熟悉的分代技術。
Young Gen被劃分為1個Eden Space和2個Suvivor Space。當物件剛剛被建立的時候,是放在Eden space。垃圾回收的時候,會掃描Eden Space和一個Suvivor Space。如果在垃圾回收的時候發現Eden Space中的物件仍然有效,則會將其複製到另外一個Suvivor Space。
就這樣不斷的掃描,最後經過多次掃描發現任然有效的物件會被放入Old Gen表示其生命週期比較長,可以減少垃圾回收時間。
![](https://img-blog.csdnimg.cn/20200525214231730.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_0,text_aHR0cDovL3d3dy5mbHlkZWFuLmNvbQ==,size_35,color_8F8F8F,t_70)
> 之後要將的幾個垃圾回收器,除了ZGC,其他都使用的是分代的技術。
好了,現在繼續講CMS,CMS是mark and swap的升級版本,它使用多個執行緒來對heap區域進行掃描,從而提升效率。
CMS在Young Generation中使用的是mark-copy,而在Old Generation主要使用的是mark-sweep。
使用CMS的命令很簡單:
~~~java
-XX:+UseConcMarkSweepGC
~~~
![](https://img-blog.csdnimg.cn/20200525221146596.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_0,text_aHR0cDovL3d3dy5mbHlkZWFuLmNvbQ==,size_35,color_8F8F8F,t_70)
上面是列出的一些CMS的調優引數。
## Serial garbage collection
Serial garbage collection使用單一的執行緒來進行垃圾回收操作,其好處就是不需要和其他的執行緒進行互動。如果你是單核的CPU,那麼最好就是選擇Serial garbage collection,因為你不能充分利用多核的好處。同樣的它也常常用在比較小型的專案中。
Serial garbage collection在Young Generation中使用的是mark-copy,而在Old Generation主要使用的是 mark-sweep-compact。
下面是開啟命令:
~~~java
-XX:+UseSerialGC
~~~
## Parallel garbage collection
和serial GC類似,它在Young Generation中使用的是mark-copy,而在Old Generation主要使用的是 mark-sweep-compact。不同的是它是並行的。
可以通過下面的命令來指定併發的執行緒:
~~~java
-XX:ParallelGCThreads=N
~~~
如果你是多核處理器,那麼Parallel GC可能是你的選擇。
> Parallel GC是JDK8中的預設GC。而在JDK9之後, G1是預設的GC。
使用下面的命令來開啟Parallel GC:
~~~java
-XX:+UseParallelGC
~~~
## G1 garbage collection
為什麼叫G1呢,G1=Garbage First,它是為替換CMS而生的,最早出現在java7中。
G1將heap區域劃分成為多個更小的區域,每個小區域都被標記成為young generation 或者old generation。從而執行GC在更小的範圍裡執行,而不是影響整個heap區域。
可以使用下面的命令來開啟:
~~~java
-XX:+UseG1GC
~~~
## Z Garbage Collection
ZGC是一個可擴充套件的,低延遲的GC。ZGC是併發的,而且不需要停止正在執行的執行緒。
使用下面的命令來開啟:
~~~java
-XX:+UseZGC
~~~
ZGC是在JDK11中被引入的。
# 怎麼選擇
小師妹:F師兄,你講了這麼多個GC,到底我該用哪個呢?
高射炮不能用來打蚊子,所以選擇合適的GC才是最終要的。這裡F師兄給你幾個建議:
1. 如果你的應用程式記憶體本來就很小,那麼使用serial collector : -XX:+UseSerialGC.
2. 如果你的程式執行在單核的CPU上,並且也沒有程式暫停時間的限制,那麼還是使用serial collector : -XX:+UseSerialGC.
3. 如果對峰值期的效能要求比較高,但是對程式暫停時間沒多大的要求,那麼可以使用 parallel collector: -XX:+UseParallelGC。
4. 如果更加關注響應時間,並且GC的對程式的暫停時間必須要小,那麼可以使用-XX:+UseG1GC。
5. 如果響應時間非常重要,並且你在使用大容量的heap空間,那麼可以考慮使用ZGC: -XX:UseZGC。
# 總結
本文介紹了幾種GC的演算法,大家可以根據需要選用。
> 本文作者:flydean程式那些事
>
> 本文連結:[http://www.flydean.com/jvm-gc-algorithms/](http://www.flydean.com/jvm-gc-algorithms/)
>
> 本文來源:flydean的部落格
>
> 歡迎關注我的公眾號:程式那些事,更多精彩等著您!