1. 程式人生 > >小師妹學JVM之:GC的垃圾回收演算法

小師妹學JVM之:GC的垃圾回收演算法

[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的部落格 > > 歡迎關注我的公眾號:程式那些事,更多精彩等著您!