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