Java虛擬機面試題精選(二)

概述

現在面試Java開發時,基本都會問到Java虛擬機的知識,根據職位不同問的內容深淺又有所區別。本文整理了10道面試中常問的Java虛擬機面試題,希望對正在面試的同學有所幫助。


11.介紹下垃圾收集機制(在什麼時候,對什麼,做了什麼)?

在什麼時候?

在觸發GC的時候,具體如下,這裏只說常見的Young GC和Full GC。

觸發Young GC:當新生代中的Eden區沒有足夠空間進行分配時會觸發Young GC。

觸發Full GC:

  1. 當準備要觸發一次Young GC時,如果發現統計數據說之前Young GC的平均晉升大小比目前老年代剩餘的空間大,則不會觸發Young GC而是轉爲觸發Full GC。(通常情況)
  2. 如果有永久代的話,在永久代需要分配空間但已經沒有足夠空間時,也要觸發一次Full GC。
  3. System.gc()默認也是觸發Full GC。
  4. heap dump帶GC默認也是觸發Full GC。
  5. CMS GC時出現Concurrent Mode Failure會導致一次Full GC的產生。

對什麼?

對那些JVM認爲已經“死掉”的對象。即從GC Root開始搜索,搜索不到的,並且經過一次篩選標記沒有復活的對象。

做了什麼?

對這些JVM認爲已經“死掉”的對象進行垃圾收集,新生代使用複製算法,老年代使用標記-清除和標記-整理算法。


12.GC Root有哪些?

在Java語言中,可作爲GC Roots的對象包括下面幾種:

  • 虛擬機棧(棧幀中的本地變量表)中引用的對象。
  • 方法區中類靜態屬性引用的對象。
  • 方法區中常量引用的對象。
  • 本地方法棧中JNI(即一般說的Native方法)引用的對象。


13.發生Young GC的時候需要掃描老年代的對象嗎?

在分代收集中,新生代的規模一般都比老年代要小許多,新生代的收集也比老年代要頻繁許多,如果回收新生代時也不得不同時掃描老年代的話,那麼Young GC的效率可能下降不少。顯然是不可能區掃描老年代的,那麼是通過什麼辦法來解決這個問題了?

在大多垃圾收集器中(G1有不同的地方),通過CardTable來維護老年代對年輕代的引用,CardTable可以說是Remembered Set(RS)的一種特殊實現,是Card的集合。Card是一塊2的冪字節大小的內存區域,例如HotSpot用512字節,裏面可能包含多個對象。CardTable要記錄的是從它覆蓋的範圍出發指向別的範圍的指針。以分代式GC的CardTable爲例,要記錄老年代指向年輕代的跨代指針,被標記的Card是老年代範圍內的。當進行年輕代的垃圾收集時,只需要掃描年輕代和老年代的CardTable即可保證不對全堆掃描也不會有遺漏。CardTable通常爲字節數組,由Card的索引(即數組下標)來標識每個分區的空間地址。


14.垃圾收集器有哪些?

目前HotSpot中有7種作用於不同分代的收集器,如下圖所示,如果兩個收集器之間存在連線,就說明它們可以搭配使用。


15.介紹CMS垃圾收集器的特點?

CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間爲目標的收集器。目前很大一部分的Java應用集中在互聯網站或者B/S系統的服務端上,這類應用尤其重視服務的響應速度,希望系統停頓時間最短,以給用戶帶來較好的體驗。CMS收集器就非常符合這類應用的需求。

從名字(包含“Mark Sweep”)上就可以看出,CMS收集器是基於“標記—清除”算法實現的,它的運作過程可以分爲6個步驟,包括:初始標記、併發標記、預處理、重新標記、併發清除、重置。

CMS是一款優秀的收集器,它的主要優點在名字上已經體現出來了:併發收集、低停頓,但是CMS還遠達不到完美的程度,它有以下3個明顯的缺點:

  1. CMS收集器對CPU資源非常敏感。
  2. CMS收集器無法處理浮動垃圾(Floating Garbage),可能出現“Concurrent Mode Failure”失敗而導致另一次Full GC的產生。
  3. CMS是一款基於“標記—清除”算法實現的收集器,這意味着收集結束時會有大量空間碎片產生。

瞭解CMS更多內容,查看我的另一篇文章:Java虛擬機:垃圾收集原理和垃圾收集器


16.介紹下G1垃圾收集器的特點?(較複雜,可以考慮跳過)

G1(Garbage-First)收集器是當今收集器技術發展的最前沿成果之一。G1是一款面向服務端應用的垃圾收集器。與其他GC收集器相比,G1具備如下特點:並行與併發、分代收集、空間整合、可預測的停頓。

在G1之前的其他收集器進行收集的範圍都是整個新生代或者老年代,而G1不再是這樣。使用G1收集器時,Java堆的內存佈局就與其他收集器有很大差別,它將整個Java堆劃分爲多個大小相等的獨立區域,雖然還保留有新生代和老年代的概念,但新生代和老年代不再是物理隔離的了,它們都是一部分Region(不需要連續)的集合。

G1收集器之所以能建立可預測的停頓時間模型,是因爲它可以有計劃地避免在整個Java堆中進行全區域的垃圾收集。G1跟蹤各個Region裏面的垃圾堆積的價值大小(回收所獲得的空間大小以及回收所需時間的經驗值),在後臺維護一個優先列表,每次根據允許的收集時間,優先回收價值最大的Region(這也就是Garbage-First名稱的來由)。這種使用Region劃分內存空間以及有優先級的區域回收方式,保證了G1收集器在有限的時間內可以獲取儘可能高的收集效率。

Mixed GC是G1垃圾收集器特有的收集方式,Mixed GC大致可劃分爲全局併發標記(global concurrent marking)拷貝存活對象(evacuation)兩個大部分:

global concurrent marking是基於SATB形式的併發標記,包括以下4個階段:初始標記(Initial Marking)、併發標記(Concurrent Marking)、最終標記(Final Marking)、清理(Clean Up)。Evacuation階段是全暫停的。它負責把一部分region裏的活對象拷貝到空region裏去,然後回收原本的region的空間。

瞭解G1更多內容,查看我的另一篇文章:Java虛擬機:垃圾收集原理和垃圾收集器


17.類加載的過程。

類從被加載到虛擬機內存中開始,到卸載出內存爲止,它的整個生命週期包括:加載、驗證、準備、解析、初始化、使用和卸載7個階段。其中驗證、準備、解析3個部分統稱爲連接。

加載:

“類加載”過程的一個階段,在加載階段,虛擬機需要完成以下3件事情:

  1. 通過一個類的全限定名來獲取定義此類的二進制字節流。
  2. 將這個字節流所代表的靜態存儲結構轉化爲方法區的運行時數據結構。
  3. 在內存中生成一個代表這個類的java.lang.Class對象,作爲方法區這個類的各種數據的訪問入口。

驗證:

連接階段的第一步,這一階段的目的是爲了確保Class文件的字節流中包含的信息符合當前虛擬機的要求,並且不會危害虛擬機自身的安全。從整體上看,驗證階段大致上會完成下面4個階段的檢驗動作:文件格式驗證、元數據驗證、字節碼驗證、符號引用驗證。

準備:

該階段是正式爲類變量(static修飾的變量)分配內存並設置類變量初始值的階段,這些變量所使用的內存都將在方法區中進行分配。這裏所說的初始值“通常情況”下是數據類型的零值,下表列出了Java中所有基本數據類型的零值。


解析:

該階段是虛擬機將常量池內的符號引用替換爲直接引用的過程。

初始化:

初始化階段是執行類構造器<clinit>()方法的過程。<clinit>()方法是由編譯器自動收集類中的所有類變量(static修飾的變量)的賦值動作和靜態語句塊(static{}塊)中的語句合併產生的,編譯器收集的順序是由語句在源文件中出現的順序所決定的。如果該類存在父類,則虛擬機會保證在執行子類的<clinit>()方法前,父類的<clinit>()方法已經執行完畢。因此在虛擬機中第一個被執行<clinit>()方法的類肯定是java.lang.Object。


18.Java虛擬機中有哪些類加載器?

從Java虛擬機的角度來講,只存在兩種不同的類加載器:一種是啓動類加載器(Bootstrap ClassLoader),這個類加載器使用C++語言實現,是虛擬機自身的一部分;另一種就是所有其他的類加載器,這些類加載器都由Java語言實現,獨立於虛擬機外部,並且全都繼承自抽象類java.lang.ClassLoader。

從Java開發人員的角度來看,絕大部分Java程序都會使用到以下3種系統提供的類加載器。

啓動類加載器(Bootstrap ClassLoader):

這個類加載器負責將存放在<JAVA_HOME>\lib目錄中的,或者被-Xbootclasspath參數所指定的路徑中的,並且是虛擬機識別的(僅按照文件名識別,如rt.jar,名字不符合的類庫即使放在lib目錄中也不會被加載)類庫加載到虛擬機內存中。

擴展類加載器(Extension ClassLoader):

這個加載器由sun.misc.Launcher$ExtClassLoader實現,它負責加載<JAVA_HOME>\lib\ext目錄中的,或者被java.ext.dirs系統變量所指定的路徑中的所有類庫,開發者可以直接使用擴展類加載器。

應用程序類加載器(Application ClassLoader):

這個類加載器由sun.misc.Launcher$AppClassLoader實現。由於這個類加載器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也稱它爲系統類加載器。它負責加載用戶類路徑(ClassPath)上所指定的類庫,開發者可以直接使用這個類加載器,如果應用程序中沒有自定義過自己的類加載器,一般情況下這個就是程序中默認的類加載器。

我們的應用程序都是由這3種類加載器互相配合進行加載的,如果有必要,還可以加入自己定義的類加載器。這些類加載器之間的關係一般如圖所示。



19.什麼是雙親委派模型?

如果一個類加載器收到了類加載的請求,它首先不會自己去嘗試加載這個類,而是把這個請求委派給父類加載器去完成,每一個層次的類加載器都是如此,因此所有的加載請求最終都應該傳送到頂層的啓動類加載器中,只有當父加載器反饋自己無法完成這個加載請求(它的搜索範圍中沒有找到所需的類)時,子加載器纔會嘗試自己去加載。


20.使用雙親委派模型的好處?

使用雙親委派模型來組織類加載器之間的關係,有一個顯而易見的好處就是Java類隨着它的類加載器一起具備了一種帶有優先級的層次關係。例如類java.lang.Object,它存放在rt.jar之中,無論哪一個類加載器要加載這個類,最終都是委派給處於模型最頂端的啓動類加載器進行加載,因此Object類在程序的各種類加載器環境中都是同一個類。相反,如果沒有使用雙親委派模型,由各個類加載器自行去加載的話,如果用戶自己編寫了一個稱爲java.lang.Object的類,並放在程序的ClassPath中,那系統中將會出現多個不同的Object類,Java 類型體系中最基礎的行爲也就無法保證,應用程序也將會變得一片混亂。


—————END—————



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章