寫在前面:java
小夥伴兒們,你們好!今天來學習Java虛擬機相關內容,做爲面試必問的知識點,來深刻了解一波!程序員
思惟導圖:面試
image-20201207153125210算法
咱們在進行垃圾回收(Garbage Collection,簡稱GC)以前確定要先判斷哪些是垃圾。緩存
在堆中幾乎放着全部的對象實例,對堆垃圾回收前的第一步就是要判斷那些對象已經死亡(即不能再被任何途徑使用的對象)。安全
image-20201207101110001服務器
給對象添加一個引用計數器,每當有一個地方引用該對象時,計數器+1,當引用失效時,計數器-1,任什麼時候候當計數器爲0的時候,該對象再也不被引用。微信
引用計數器這個方法實現簡單,斷定效率也高。可是,當前主流的虛擬機都沒有采用這個算法來管理內存,其中最主要的緣由是它很難解決對象之間互相循環引用的問題。多線程
所謂對象之間互相循環引用,以下面代碼所示:除了對象 objA 和 objB 相互引用着對方以外,這兩個對象之間再無任何引用。可是它們由於互相引用對方,致使它們的引用計數器都不爲 0,因而引用計數算法沒法通知 GC 回收器回收他們。閉包
public class ReferenceCountingGc {
public Object instance = null;
public static final int _1MB = 1024*1024;
public static void main(String[] args) {
ReferenceCountingGc objA = new ReferenceCountingGc();
ReferenceCountingGc objB = new ReferenceCountingGc();
objA.instance = objB;
objB.instance = objA;
objA = null;
objB = null;
}
}
這個算法的基本思想就是經過一系列的稱爲 「GC Roots」 的對象做爲起點,從這些節點開始向下搜索,節點所走過的路徑稱爲引用鏈,當一個對象到 GC Roots 沒有任何引用鏈相連的話,則證實此對象是不可用的。
image-20201206094626579
在Java語言中,可做爲GC Roots的對象包括下面幾種:
不管是經過引用計數法判斷對象引用數量,仍是經過可達性分析法判斷對象的引用鏈是否可達,斷定對象的存活都與「引用」有關。
JDK1.2 以前,Java 中引用的定義很傳統:若是 reference
類型的數據存儲的數值表明的是另外一塊內存的起始地址,就稱這塊內存表明一個引用。 JDK1.2 之後,Java 對引用的概念進行了擴充,將引用分爲強引用、軟引用、弱引用、虛引用四種(引用強度逐漸減弱)。
之前咱們使用的大部分引用實際上都是強引用,這是使用最廣泛的引用。相似於「Object obj=new Object()」這類的引用,若是一個對象具備強引用,那就相似於必不可少的生活用品,垃圾回收器毫不會回收它。當內存空間不足,Java虛擬機寧願拋出OutOfMemoryError
錯誤,使程序異常終止,也不會靠隨意回收具備強引用的對象來解決內存不足問題。
若是一個對象只具備軟引用,那就相似於無關緊要的生活用品。若是內存空間足夠,垃圾回收器就不會回收它,若是內存空間不足了,就會回收這些對象的內存。只要垃圾回收器沒有回收它,該對象就能夠被程序使用。軟引用可用來實現內存敏感的高速緩存。
軟引用能夠和一個引用隊列(ReferenceQueue
)聯合使用,若是軟引用所引用的對象被垃圾回收,JAVA 虛擬機就會把這個軟引用加入到與之關聯的引用隊列中。
若是一個對象只具備弱引用,那就相似於無關緊要的生活用品。弱引用與軟引用的區別在於:只具備弱引用的對象擁有更短暫的生命週期。在垃圾回收器線程掃描它所管轄的內存區域的過程當中,一旦發現了只具備弱引用的對象,無論當前內存空間足夠與否,都會回收它的內存。不過,因爲垃圾回收器是一個優先級很低的線程, 所以不必定會很快發現那些只具備弱引用的對象。
弱引用能夠和一個引用隊列(ReferenceQueue
)聯合使用,若是弱引用所引用的對象被垃圾回收,Java 虛擬機就會把這個弱引用加入到與之關聯的引用隊列中。
"虛引用"顧名思義,就是形同虛設,與其餘幾種引用都不一樣,虛引用並不會決定對象的生命週期。若是一個對象僅持有虛引用,那麼它就和沒有任何引用同樣,在任什麼時候候均可能被垃圾回收。
虛引用主要用來跟蹤對象被垃圾回收的活動。虛引用與軟引用和弱引用的一個區別在於:虛引用必須和引用隊列(ReferenceQueue)聯合使用。當垃圾回收器準備回收一個對象時,若是發現它還有虛引用,就會在回收對象的內存以前,把這個虛引用加入到與之關聯的引用隊列中。程序能夠經過判斷引用隊列中是否已經加入了虛引用,來了解被引用的對象是否將要被垃圾回收。程序若是發現某個虛引用已經被加入到引用隊列,那麼就能夠在所引用的對象的內存被回收以前採起必要的行動。
特別注意,在世紀程序設計中通常不多使用弱引用與虛引用,使用軟用的狀況較多,這是由於軟引用能夠加速JVM對垃圾內存的回收速度,能夠維護系統的運行安全,防止內存溢出(OutOfMemory
)等問題的產生。
運行時常量池主要回收的是廢棄的常量。那麼,咱們如何判斷一個常量是廢棄常量呢?
假如在常量池中存在字符串"abc
" ,若是當前沒有任何String
對象引用該字符串常量的話,就說明常量"abc
"就是廢棄常量,若是這時發生內存回收的話並且有必要的話," abc
"就會被系統清理出常量池。
斷定一個常量是不是「廢棄常量」比較簡單,而要斷定一個類是不是「無用的類」的條件則相對苛刻許多。方法區主要回收無用的類,類須要同時知足下面3個條件才能算是 「無用的類」 :
ClassLoader
已經被回收。java.lang.Class
對象沒有在任何地方被引用,沒法在任何地方經過反射訪問該類的方法。虛擬機能夠對知足上述3個條件的無用類進行回收,這裏說的僅僅是「能夠」,而並非和對象同樣不使用了就會必然被回收。
該算法分爲「標記」和「清除」階段:首先標記出全部不須要回收的對象,在標記完成後統一回收掉全部沒有被標記的對象。它是最基礎的收集算法,後續的算法都是對其不足進行改進獲得。這種垃圾收集算法會帶來兩個明顯的問題:
標記-清除算法
爲了解決效率問題,「複製」收集算法出現了。它將可用內存分爲大小相同的兩塊,每次使用其中的一塊。當這一塊的內存使用完後,就將還存活的對象複製到另外一塊去,而後再把使用的空間一次清理掉。這樣就使每次的內存回收都是對內存區間的一半進行回收。實現簡單,運行高效。
複製算法
根據老年代的特色提出的一種標記算法,標記過程仍然與「標記-清除」算法同樣,但後續步驟不是直接對可回收對象回收,而是讓全部存活的對象向一端移動,而後直接清理掉端邊界之外的內存。
標記-整理算法
當前虛擬機的垃圾收集都採用分代收集算法,這種算法沒有什麼新的思想,只是根據對象存活週期的不一樣將內存分爲幾塊。通常將 java 堆分爲新生代和老年代,這樣咱們就能夠根據各個年代的特色選擇合適的垃圾收集算法。
好比在新生代中,每次收集都會有大量對象死去,因此能夠選擇複製算法,只須要付出少許對象的複製成本就能夠完成每次垃圾收集。而老年代的對象存活概率是比較高的,並且沒有額外的空間對它進行分配擔保,因此咱們必須選擇「標記-清除」或「標記-整理」算法進行垃圾收集。
若是說收集算法是內存回收的方法論,那麼垃圾收集器就是內存回收的具體實現。
雖然咱們對各個收集器進行比較,但並不是要挑選出一個最好的收集器。由於直到如今爲止尚未最好的垃圾收集器出現,更加沒有萬能的垃圾收集器,咱們能作的就是根據具體應用場景選擇適合本身的垃圾收集器。試想一下:若是有一種四海以內、任何場景下都適用的完美收集器存在,那麼咱們的 HotSpot 虛擬機就不會實現那麼多不一樣的垃圾收集器了。
常見的垃圾收集器
Serial
收集器Serial
收集器是最基本、歷史最悠久的垃圾收集器了。從名字上看是串行的意思,這個收集器是一個單線程的新生代收集器。它的 「單線程」 的意義不只僅意味着它只會使用一條垃圾收集線程去完成垃圾收集工做,更重要的是它在進行垃圾收集工做的時候必須暫停其餘全部的工做線程( "Stop The World" ),直到它收集結束。
Serial收集器
Serial 採起 「複製算法」 實現,若是是在單 CPU 環境下,Serial 收集器沒有線程交互的開銷,理論上是能夠得到最高的單線程執行效率,STW 的時間也能夠控制在幾十到幾百毫秒內,這個時間是徹底能夠接受的。
與其餘單線程收集器相比它的優勢就是:它簡單而高效(與其餘收集器的單線程相比)。 簡單而高效 Serial 收集器因爲沒有線程交互的開銷,天然能夠得到很高的單線程收集效率。 Serial 收集器對於運行在 Client 模式下的虛擬機來講是個不錯的選擇。
ParNew
收集器ParNew 收集器其實就是 Serial 收集器的多線程版本,除了使用多線程進行垃圾收集外,其他行爲(控制參數、收集算法、回收策略等等)和 Serial 收集器徹底同樣。ParNew 收集器雖然有多線程優點,但在單 CPU 和多 CPU 環境下,效果並不必定會比 Serial 好,至少在單 CPU 環境下是確定不如的 Serial 的。因爲線程交互開銷的時間,效果並不如人意,多線程的好處在於更高效率地利用 CPU ,提升 CPU 的吞吐量,讓 CPU 空閒的時間減小。
新生代採用複製算法,老年代採用標記-整理算法。
ParNew收集器
它是許多運行在 Server 模式下的虛擬機的首要選擇,除了 Serial 收集器外,只有它能與 CMS 收集器(真正意義上的併發收集器,後面會介紹到)配合工做。
Parallel Scavenge
收集器Parallel Scavenge 收集器也是使用複製算法的多線程收集器,它看上去幾乎和ParNew都同樣。 那麼它有什麼特別之處呢?
Parallel Scavenge 收集器關注點是吞吐量(高效率的利用 CPU)。CMS 等垃圾收集器的關注點更多的是用戶線程的停頓時間(提升用戶體驗)。所謂吞吐量就是 CPU 中用於運行用戶代碼的時間與 CPU 總消耗時間的比值。 Parallel Scavenge 收集器提供了不少參數供用戶找到最合適的停頓時間或最大吞吐量,若是對於收集器運做不太瞭解,手工優化存在困難的時候,使用Parallel Scavenge收集器配合自適應調節策略,把內存管理優化交給虛擬機去完成也是一個不錯的選擇。
新生代採用複製算法,老年代採用標記-整理算法。
Parallel Scavenge收集器
這是 JDK1.8 默認收集器
使用java -XX:+PrintCommandLineFlags -version
命令查看
-XX:InitialHeapSize=197918400 -XX:MaxHeapSize=3166694400 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
java version "1.8.0_144"
Java(TM) SE Runtime Environment (build 1.8.0_144-b01)
Java HotSpot(TM) 64-Bit Server VM (build 25.144-b01, mixed mode)
查看結果
JDK1.8 默認使用的是 Parallel Scavenge + Parallel Old
,若是指定了-XX:+UseParallelGC
參數,則默認指定了-XX:+UseParallelOld GC
,可使用-XX:-UseParallelOldGC
來禁用該功能。
Serial Old
收集器Serial 收集器的老年代版本,它一樣是一個單線程收集器。它主要有兩大用途:一種用途是在 JDK1.5 以及之前的版本中與 Parallel Scavenge 收集器搭配使用,另外一種用途是做爲 CMS 收集器的後備方案。
Parallel Old
收集器Parallel Scavenge 收集器的老年代版本。使用多線程和「標記-整理」算法。在注重吞吐量以及 CPU 資源的場合,均可以優先考慮 Parallel Scavenge 收集器和 Parallel Old 收集器。
CMS
收集器CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間爲目標的收集器。它很是符合在注重用戶體驗的應用上使用。
CMS(Concurrent Mark Sweep)收集器是 HotSpot 虛擬機第一款真正意義上的併發收集器,它第一次實現了讓垃圾收集線程與用戶線程(基本上)同時工做。
從名字中的Mark Sweep這兩個詞能夠看出,CMS 收集器是一種 「標記-清除」算法實現的,它的運做過程相比於前面幾種垃圾收集器來講更加複雜一些。整個過程分爲四個步驟:
CMS收集器
兩次STW,從它的名字就能夠看出它是一款優秀的垃圾收集器,主要優勢:併發收集、低停頓。可是它有下面三個明顯的缺點:
什麼是浮動垃圾? CMS在併發清理階段,用戶線程還在運行, 伴隨着程序的運行天然也會產生新的垃圾,這一部分垃圾產生在標記過程以後,CMS沒法再當次過程當中處理,因此只有等到下次gc時候在清理掉,這一部分垃圾就稱做「浮動垃圾」。
G1
收集器G1 (Garbage-First) 是一款面向服務器的垃圾收集器,主要針對配備多顆處理器及大容量內存的機器. 以極高機率知足 GC 停頓時間要求的同時,還具有高吞吐量性能特徵。
被視爲 JDK1.7 中 HotSpot 虛擬機的一個重要進化特徵。它具有一下特色:
G1收集器運行示意圖
G1 收集器在後臺維護了一個優先列表,每次根據容許的收集時間,優先選擇回收價值最大的 Region(這也就是它的名字 Garbage-First 的由來)。這種使用 Region 劃份內存空間以及有優先級的區域回收方式,保證了 G1 收集器在有限時間內能夠儘量高的收集效率(把內存化整爲零)。
微信搜索公衆號《程序員的時光》 好了,今天就先分享到這裏了,下期繼續給你們帶來JVM面試內容! 更多幹貨、優質文章,歡迎關注個人原創技術公衆號~
參考文獻:
深刻理解Java虛擬機(第2版).周志明