天天看點

面試官:你對JVM垃圾收集器了解嗎?13連問你是否抗的住!

關于JVM垃圾收集器的面試題

1、簡述Java垃圾回收機制

2、GC是什麼?為什麼要GC

3、垃圾回收的優點和原理。并考慮2種回收機制

4、垃圾回收器的基本原理是什麼?垃圾回收器可以馬上回收記憶體嗎?有什麼辦法主動通知虛拟機進行垃圾回收?

5、Java 中都有哪些引用類型?

6、怎麼判斷對象是否可以被回收?

7、在Java中,對象什麼時候可以被垃圾回收

8、JVM中的永久代中會發生垃圾回收嗎

9、說一下 JVM 有哪些垃圾回收算法?

10、說一下 JVM 有哪些垃圾回收器?

11、詳細介紹一下 CMS 垃圾回收器?

12、新生代垃圾回收器和老年代垃圾回收器都有哪些?有什麼差別?

13、簡述分代垃圾回收器是怎麼工作的?

在java中,程式員是不需要顯示的去釋放一個對象的記憶體的,而是由虛拟機自行執行。在JVM中,有一個垃圾回收線程,它是低優先級的,在正常情況下是不會執行的,隻有在虛拟機空閑或者目前堆記憶體不足時,才會觸發執行,掃面那些沒有被任何引用的對象,并将它們添加到要回收的集合中,進行回收。

GC 是垃圾收集的意思(Gabage Collection),記憶體處理是程式設計人員容易出現問題的地方,忘記或者錯誤的記憶體

回收會導緻程式或系統的不穩定甚至崩潰,Java 提供的 GC 功能可以自動監測對象是否超過作用域進而達到自動

回收記憶體的目的,Java 語言沒有提供釋放已配置設定記憶體的顯示操作方法。

Java語言最顯著的特點就是引入了垃圾回收機制,它使java程式員在編寫程式時不再考慮記憶體管理的問題。

由于有這個垃圾回收機制,java中的對象不再有“作用域”的概念,隻有引用的對象才有“作用域”。

垃圾回收機制有效的防止了記憶體洩露,可以有效的使用可使用的記憶體。

垃圾回收器通常作為一個單獨的低級别的線程運作,在不可預知的情況下對記憶體堆中已經死亡的或很長時間沒有用過的對象進行清除和回收。

程式員不能實時的對某個對象或所有對象調用垃圾回收器進行垃圾回收。

垃圾回收有分代複制垃圾回收、标記垃圾回收、增量垃圾回收。

對于GC來說,當程式員建立對象時,GC就開始監控這個對象的位址、大小以及使用情況。

通常,GC采用有向圖的方式記錄和管理堆(heap)中的所有對象。通過這種方式确定哪些對象是"可達的",哪些對象是"不可達的"。當GC确定一些對象為"不可達"時,GC就有責任回收這些記憶體空間。

可以。程式員可以手動執行System.gc(),通知GC運作,但是Java語言規範并不保證GC一定會執行。

  • 強引用:發生 gc 的時候不會被回收。
  • 軟引用:有用但不是必須的對象,在發生記憶體溢出之前會被回收。
  • 弱引用:有用但不是必須的對象,在下一次GC時會被回收。
  • 虛引用(幽靈引用/幻影引用):無法通過虛引用獲得對象,用 PhantomReference 實作虛引用,虛引用的用途是在 gc 時傳回一個通知。

垃圾收集器在做垃圾回收的時候,首先需要判定的就是哪些記憶體是需要被回收的,哪些對象是「存活」的,是不可以被回收的;哪些對象已經「死掉」了,需要被回收。歡迎關注公種浩:程式員追風,領取Java知識點學習思維導圖總結+一線大廠Java面試題總結+一份300頁pdf文檔的Java核心知識點總結!

一般有兩種方法來判斷:

  • 引用計數器法:為每個對象建立一個引用計數,有對象引用時計數器 +1,引用被釋放時計數 -1,當計數器為 0 時就可以被回收。它有一個缺點不能解決循環引用的問題;
  • 可達性分析算法:從 GC Roots 開始向下搜尋,搜尋所走過的路徑稱為引用鍊。當一個對象到 GC Roots 沒有任何引用鍊相連時,則證明此對象是可以被回收的。

當對象對目前使用這個對象的應用程式變得不可觸及的時候,這個對象就可以被回收了。

垃圾回收不會發生在永久代,如果永久代滿了或者是超過了臨界值,會觸發完全垃圾回收(Full GC)。如果你仔細檢視垃圾收集器的輸出資訊,就會發現永久代也是被回收的。這就是為什麼正确的永久代大小對避免Full GC是非常重要的原因。

垃圾回收不會發生在永久代,如果永久代滿了或者是超過了臨界值,會觸發完全垃圾回收(Full GC)。如果你仔細檢視垃圾收集器的輸出資訊,就會發現永久代也是被回收的。這就是為什麼正确的永久代大小對避免Full GC是非常重要的原因。請參考下Java8:從永久代到中繼資料區

(譯者注:Java8中已經移除了永久代,新加了一個叫做中繼資料區的native記憶體區)

  • 标記-清除算法:标記無用對象,然後進行清除回收。缺點:效率不高,無法清除垃圾碎片。
  • 複制算法:按照容量劃分二個大小相等的記憶體區域,當一塊用完的時候将活着的對象複制到另一塊上,然後再把已使用的記憶體空間一次清理掉。缺點:記憶體使用率不高,隻有原來的一半。
  • 标記-整理算法:标記無用對象,讓所有存活的對象都向一端移動,然後直接清除掉端邊界以外的記憶體。
  • 分代算法:根據對象存活周期的不同将記憶體劃分為幾塊,一般是新生代和老年代,新生代基本采用複制算法,老年代采用标記整理算法。

标記-清除算法

标記無用對象,然後進行清除回收。

标記-清除算法(Mark-Sweep)是一種常見的基礎垃圾收集算法,它将垃圾收集分為兩個階段:

  • 标記階段:标記出可以回收的對象。
  • 清除階段:回收被标記的對象所占用的空間。

标記-清除算法之是以是基礎的,是因為後面講到的垃圾收集算法都是在此算法的基礎上進行改進的。

優點:實作簡單,不需要對象進行移動。

缺點:标記、清除過程效率低,産生大量不連續的記憶體碎片,提高了垃圾回收的頻率。

标記-清除算法的執行的過程如下圖所示

複制算法

為了解決标記-清除算法的效率不高的問題,産生了複制算法。它把記憶體空間劃為兩個相等的區域,每次隻使用其中一個區域。垃圾收集時,周遊目前使用的區域,把存活對象複制到另外一個區域中,最後将目前使用的區域的可回收的對象進行回收。

優點:按順序配置設定記憶體即可,實作簡單、運作高效,不用考慮記憶體碎片。

缺點:可用的記憶體大小縮小為原來的一半,對象存活率高時會頻繁進行複制。

複制算法的執行過程如下圖所示

标記-整理算法

在新生代中可以使用複制算法,但是在老年代就不能選擇複制算法了,因為老年代的對象存活率會較高,這樣會有較多的複制操作,導緻效率變低。标記-清除算法可以應用在老年代中,但是它效率不高,在記憶體回收後容易産生大量記憶體碎片。是以就出現了一種标記-整理算法(Mark-Compact)算法,與标記-整理算法不同的是,在标記可回收的對象後将所有存活的對象壓縮到記憶體的一端,使他們緊湊的排列在一起,然後對端邊界以外的記憶體進行回收。回收後,已用和未用的記憶體都各自一邊。

優點:解決了标記-清理算法存在的記憶體碎片問題。

缺點:仍需要進行局部對象移動,一定程度上降低了效率。

标記-整理算法的執行過程如下圖所示

分代收集算法

目前商業虛拟機都采用分代收集的垃圾收集算法。分代收集算法,顧名思義是根據對象的存活周期将記憶體劃分為幾塊。一般包括年輕代、老年代 和 永久代,如圖所示:

如果說垃圾收集算法是記憶體回收的方法論,那麼垃圾收集器就是記憶體回收的具體實作。下圖展示了7種作用于不同分代的收集器,其中用于回收新生代的收集器包括Serial、PraNew、Parallel Scavenge,回收老年代的收集器包括Serial Old、Parallel Old、CMS,還有用于回收整個Java堆的G1收集器。不同收集器之間的連線表示它們可以搭配使用。

  • Serial收集器(複制算法): 新生代單線程收集器,标記和清理都是單線程,優點是簡單高效;
  • ParNew收集器 (複制算法): 新生代收并行集器,實際上是Serial收集器的多線程版本,在多核CPU環境下有着比Serial更好的表現;
  • Parallel Scavenge收集器 (複制算法): 新生代并行收集器,追求高吞吐量,高效利用 CPU。吞吐量 = 使用者線程時間/(使用者線程時間+GC線程時間),高吞吐量可以高效率的利用CPU時間,盡快完成程式的運算任務,适合背景應用等對互動相應要求不高的場景;
  • Serial Old收集器 (标記-整理算法): 老年代單線程收集器,Serial收集器的老年代版本;
  • Parallel Old收集器 (标記-整理算法): 老年代并行收集器,吞吐量優先,Parallel Scavenge收集器的老年代版本;
  • CMS(Concurrent Mark Sweep)收集器(标記-清除算法): 老年代并行收集器,以擷取最短回收停頓時間為目标的收集器,具有高并發、低停頓的特點,追求最短GC回收停頓時間。
  • G1(Garbage First)收集器 (标記-整理算法): Java堆并行收集器,G1收集器是JDK1.7提供的一個新收集器,G1收集器基于“标記-整理”算法實作,也就是說不會産生記憶體碎片。此外,G1收集器不同于之前的收集器的一個重要特點是:G1回收的範圍是整個Java堆(包括新生代,老年代),而前六種收集器回收的範圍僅限于新生代或老年代。

CMS 是英文 Concurrent Mark-Sweep 的簡稱,是以犧牲吞吐量為代價來獲得最短回收停頓時間的垃圾回收器。對于要求伺服器響應速度的應用上,這種垃圾回收器非常适合。在啟動 JVM 的參數加上“-XX:+UseConcMarkSweepGC”來指定使用 CMS 垃圾回收器。

CMS 使用的是标記-清除的算法實作的,是以在 gc 的時候回産生大量的記憶體碎片,當剩餘記憶體不能滿足程式運作要求時,系統将會出現 Concurrent Mode Failure,臨時 CMS 會采用 Serial Old 回收器進行垃圾清除,此時的性能将會被降低。

  • 新生代回收器:Serial、ParNew、Parallel Scavenge
  • 老年代回收器:Serial Old、Parallel Old、CMS
  • 整堆回收器:G1

新生代垃圾回收器一般采用的是複制算法,複制算法的優點是效率高,缺點是記憶體使用率低;老年代回收器一般采用的是标記-整理的算法進行垃圾回收。

分代回收器有兩個分區:老生代和新生代,新生代預設的空間占比總空間的 1/3,老生代的預設占比是 2/3。

新生代使用的是複制算法,新生代裡有 3 個分區:Eden、To Survivor、From Survivor,它們的預設占比是 8:1:1,它的執行流程如下:歡迎關注公種浩:程式員追風,領取Java知識點學習思維導圖總結+一線大廠Java面試題總結+一份300頁pdf文檔的Java核心知識點總結!

  • 把 Eden + From Survivor 存活的對象放入 To Survivor 區;
  • 清空 Eden 和 From Survivor 分區;
  • From Survivor 和 To Survivor 分區交換,From Survivor 變 To Survivor,To Survivor 變 From Survivor。

每次在 From Survivor 到 To Survivor 移動時都存活的對象,年齡就 +1,當年齡到達 15(預設配置是 15)時,更新為老生代。大對象也會直接進入老生代。

老生代當空間占用到達某個值之後就會觸發全局垃圾收回,一般使用标記整理的執行算法。以上這些循環往複就構成了整個分代垃圾回收的整體執行流程。

最後

歡迎大家一起交流,喜歡文章記得關注我點個贊喲,感謝支援!