想進大廠必懂JVM,分享詳解十八道JVM高頻面試題!

JVM

JVM是Java Virtual Machine(Java虛擬機)的縮寫,JVM是一種用於計算設備的規範,它是一個虛構出來的計算機,是通過在實際的計算機上仿真模擬各種計算機功能來實現的。Java虛擬機包括一套字節碼指令集、一組寄存器、一個棧、一個垃圾回收堆和一個存儲方法域。 JVM屏蔽了與具體操作系統平臺相關的信息,使Java程序只需生成在Java虛擬機上運行的目標代碼(字節碼),就可以在多種平臺上不加修改地運行。JVM在執行字節碼時,實際上最終還是把字節碼解釋成具體平臺上的機器指令執行。

Java語言的一個非常重要的特點就是與平臺的無關性。而使用Java虛擬機是實現這一特點的關鍵。一般的高級語言如果要在不同的平臺上運行,至少需要編譯成不同的目標代碼。而引入Java語言虛擬機後,Java語言在不同平臺上運行時不需要重新編譯。Java語言使用Java虛擬機屏蔽了與具體平臺相關的信息,使得Java語言編譯程序只需生成在Java虛擬機上運行的目標代碼(字節碼),就可以在多種平臺上不加修改地運行。Java虛擬機在執行字節碼時,把字節碼解釋成具體平臺上的機器指令執行。這就是Java的能夠“一次編譯,到處運行”的原因。

分享詳細解析十八道Java面試時常問的JVM題,希望對各位有所幫助,另外我針對當前互聯網面試總結了一套“金四銀五Java面試突擊題庫”給大家,文末有領取方式(誠意滿滿)


1、內存模型以及分區,需要詳細到每個區放什麼

JVM 分爲堆區和棧區,還有方法區,初始化的對象放在堆裏面引用放在棧裏面,class 類信息常量池(static 常量和 static 變量)等放在方法區

  • 方法區:主要是存儲類信息,常量池(static 常量和 static 變量),編譯後的代碼(字節碼)等數據
  • 堆:初始化的對象,成員變量 (那種非 static 的變量),所有的對象實例和數組都要在堆上分配
  • 棧:棧的結構是棧幀組成的,調用一個方法就壓入一幀,幀上面存儲局部變量表,操作數棧,方法出口等信息,局部變量表存放的是 8 大基礎類型加上一個應用類型,所以還是一個指向地址的指針
  • 本地方法棧:主要爲 Native 方法服務
  • 程序計數器:記錄當前線程執行的行號

2、堆裏面的分區:Eden,survival (from+ to),老年代,各自的特點

堆裏面分爲新生代和老生代(java8 取消了永久代,採用了 Metaspace),新生代包含 Eden+Survivor 區,survivor 區裏面分爲 from 和 to 區,內存回收時,如果用的是複製算法,從 from 複製到 to,當經過一次或者多次 GC 之後,存活下來的對象會被移動到老年區,當 JVM 內存不夠用的時候,會觸發 Full GC,清理 JVM 老年區當新生區滿了之後會觸發 YGC,先把存活的對象放到其中一個 Survice區,然後進行垃圾清理。

因爲如果僅僅清理需要刪除的對象,這樣會導致內存碎片,因此一般會把 Eden 進行完全的清理,然後整理內存。那麼下次 GC 的時候,就會使用下一個 Survive,這樣循環使用。如果有特別大的對象,新生代放不下,就會使用老年代的擔保,直接放到老年代裏面。因爲 JVM 認爲,一般大對象的存活時間一般比較久遠。

3、對象創建方法,對對象的內存分配,對象的訪問定位

new 一個對象

4、GC 的兩種判定方法

  • 引用計數法:指的是如果某個地方引用了這個對象就+1,如果失效了就-1,當爲 0 就會回收但是 JVM 沒有用這種方式,因爲無法判定相互循環引用(A 引用 B,B 引用 A)的情況
  • 引用鏈法: 通過一種 GC ROOT 的對象(方法區中靜態變量引用的對象等-static 變量)來判斷,如果有一條鏈能夠到達 GC ROOT 就說明,不能到達 GC ROOT 就說明可以回收

5、SafePoint 是什麼

比如 GC 的時候必須要等到 Java 線程都進入到 safepoint 的時候 VMThread 才能開始執行 GC,

  1. 循環的末尾 (防止大循環的時候一直不進入 safepoint,而其他線程在等待它進入safepoint)
  2. 方法返回前
  3. 調用方法的 call 之後
  4. 拋出異常的位置

6、GC 的三種收集方法:標記清除、標記整理、複製算法的原理與特點,分別用在什麼地方,如果讓你優化收集方法,有什麼思路?

先標記,標記完畢之後再清除,效率不高,會產生碎片複製算法:分爲 8:1 的 Eden 區和 survivor 區,就是上面談到的 YGC
標記整理:標記完畢之後,讓所有存活的對象向一端移動

7、GC 收集器有哪些?CMS 收集器與 G1 收集器的特點

  • 並行收集器:串行收集器使用一個單獨的線程進行收集,GC 時服務有停頓時間
  • 串行收集器:次要回收中使用多線程來執行

CMS 收集器是基於“標記—清除”算法實現的,經過多次標記纔會被清除G1 從整體來看是基於“標記—整理”算法實現的收集器,從**局部(兩個 Region 之間)上來看是基於“複製”**算法實現的

8、Minor GC 與 Full GC 分別在什麼時候發生?

新生代內存不夠用時候發生 MGC 也叫 YGCJVM 內存不夠的時候發生 FGC

9、幾種常用的內存調試工具:jmap、jstack、jconsole、jhat

jstack 可以看當前棧的情況,jmap 查看內存,jhat 進行 dump 堆的信息
mat(eclipse 的也要了解一下)

10、類加載的幾個過程:

加載、驗證、準備、解析、初始化。然後是使用和卸載了
通過全限定名來加載生成 class 對象到內存中,然後進行驗證這個 class 文件,包括文件格式校驗、元數據驗證,字節碼校驗等。準備是對這個對象分配內存。解析是將符號引用轉化爲直接引用(指針引用),初始化就是開始執行構造器的代碼

11、JVM 內存每個區的作用是什麼?

java 虛擬機主要分爲以下一個區:

方法區

  1. 有時候也成爲永久代,在該區內很少發生垃圾回收,但是並不代表不發生 GC,在這裏進行的 GC 主要是對方法區裏的常量池和對類型的卸載
  2. 方法區主要用來存儲已被虛擬機加載的類的信息、常量、靜態變量和即時編譯器編譯後的代碼等數據。
  3. 該區域是被線程共享的。
  4. 方法區裏有一個運行時常量池,用於存放靜態編譯產生的字面量和符號引用。該常量池具有動態性,也就是說常量並不一定是編譯時確定,運行時生成的常量也會存在這個常量池中。

虛擬機棧

  1. 虛擬機棧也就是我們平常所稱的棧內存,它爲 java 方法服務,每個方法在執行的時候都會創建一個棧幀,用於存儲局部變量表、操作數棧、動態鏈接和方法出口等信息。
  2. 虛擬機棧是線程私有的,它的生命週期與線程相同。
  3. 局部變量表裏存儲的是基本數據類型、returnAddress 類型(指向一條字節碼指令的地址)和對象引用,這個對象引用有可能是指向對象起始地址的一個指針,也有可能是代表對象的句柄或者與對象相關聯的位置。局部變量所需的內存空間在編譯器間確定
  4. 操作數棧的作用主要用來存儲運算結果以及運算的操作數,它不同於局部變量表通過索引來訪問,而是壓棧和出棧的方式
  5. 每個棧幀都包含一個指向運行時常量池中該棧幀所屬方法的引用,持有這個引用是爲了支持方法調用過程中的動態連接.動態鏈接就是將常量池中的符號引用在運行期轉化爲直接引用。

本地方法棧
本地方法棧和虛擬機棧類似,只不過本地方法棧爲 Native 方法服務。


java 堆是所有線程所共享的一塊內存,在虛擬機啓動時創建,幾乎所有的對象實例都在這裏創建,因此該區域經常發生垃圾回收操作。

程序計數器
內存空間小,字節碼解釋器工作時通過改變這個計數值可以選取下一條需要執行的字節碼指令,分支、循環、跳轉、異常處理和線程恢復等功能都需要依賴這個計數器完成。該內存區域是唯一一個 java 虛擬機規範沒有規定任何 OOM 情況的區域。

12、如和判斷一個對象是否存活?(或者 GC 對象的判定方法)

判斷一個對象是否存活有兩種方法:

1. 引用計數法
所謂引用計數法就是給每一個對象設置一個引用計數器,每當有一個地方引用這個對象時,就將計數器加一,引用失效時,計數器就減一。當一個對象的引用計數器爲零時,說明此對象沒有被引用,也就是“死對象”,將會被垃圾回收.引用計數法有一個缺陷就是無法解決循環引用問題,也就是說當對象 A 引用對象 B,對象B 又引用者對象 A,那麼此時 A,B 對象的引用計數器都不爲零,也就造成無法完成垃圾回收,所以主流的虛擬機都沒有采用這種算法。

2. 可達性算法(引用鏈法)
該算法的思想是:從一個被稱爲 GC Roots 的對象開始向下搜索,如果一個對象到 GCRoots 沒有任何引用鏈相連時,則說明此對象不可用。

在 java 中可以作爲 GC Roots 的對象有以下幾種:

  • 虛擬機棧中引用的對象
  • 方法區類靜態屬性引用的對象
  • 方法區常量池引用的對象
  • 本地方法棧 JNI 引用的對象

雖然這些算法可以判定一個對象是否能被回收,但是當滿足上述條件時,一個對象比不一定會被回收。當一個對象不可達 GC Root 時,這個對象並不會立馬被回收,而是出於一個死緩的階段,若要被真正的回收需要經歷兩次標記如果對象在可達性分析中沒有與 GC Root 的引用鏈,那麼此時就會被第一次標記並且進行一次篩選,篩選的條件是是否有必要執行 finalize()方法。當對象沒有覆蓋 finalize()方法或者已被虛擬機調用過,那麼就認爲是沒必要的。

如果該對象有必要執行 finalize()方法,那麼這個對象將會放在一個稱爲 F-Queue 的對隊列中,虛擬機會觸發一個 Finalize()線程去執行,此線程是低優先級的,並且虛擬機不會承諾一直等待它運行完,這是因爲如果 finalize()執行緩慢或者發生了死鎖,那麼就會造成 FQueue 隊列一直等待,造成了內存回收系統的崩潰。GC 對處於 F-Queue 中的對象進行第二次被標記,這時,該對象將被移除”即將回收”集合,等待回收。

13、簡述 java 垃圾回收機制?

在 java 中,程序員是不需要顯示的去釋放一個對象的內存的,而是由虛擬機自行執行。在JVM 中,有一個垃圾回收線程,它是低優先級的,在正常情況下是不會執行的,只有在虛擬機空閒或者當前堆內存不足時,纔會觸發執行,掃面那些沒有被任何引用的對象,並將它們添加到要回收的集合中,進行回收。

14、java 中垃圾收集的方法有哪些?

1.標記-清除
這是垃圾收集算法中最基礎的,根據名字就可以知道,它的思想就是標記哪些要被回收的對象,然後統一回收。這種方法很簡單,但是會有兩個主要問題:1.效率不高,標記和清除的效率都很低;2.會產生大量不連續的內存碎片,導致以後程序在分配較大的對象時,由於沒有充足的連續內存而提前觸發一次 GC 動作。

2.複製算法
爲了解決效率問題,複製算法將可用內存按容量劃分爲相等的兩部分,然後每次只使用其中的一塊,當一塊內存用完時,就將還存活的對象複製到第二塊內存上,然後一次性清楚完第一塊內存,再將第二塊上的對象複製到第一塊。

但是這種方式,內存的代價太高,每次基本上都要浪費一般的內存。於是將該算法進行了改進,內存區域不再是按照 1:1 去劃分,而是將內存劃分爲8:1:1 三部分,較大那份內存交 Eden 區,其餘是兩塊較小的內存區叫 Survior 區。每次都會優先使用 Eden 區,若 Eden 區滿,就將對象複製到第二塊內存區上,然後清除 Eden 區,如果此時存活的對象太多,以至於 Survivor 不夠時,會將這些對象通過分配擔保機制複製到老年代中。(java 堆又分爲新生代和老年代)

3.標記-整理
該算法主要是爲了解決標記-清除,產生大量內存碎片的問題;當對象存活率較高時,也解決了複製算法的效率問題。它的不同之處就是在清除對象的時候現將可回收對象移動到一端,然後清除掉端邊界以外的對象,這樣就不會產生內存碎片了。

4.分代收集
現在的虛擬機垃圾收集大多采用這種方式,它根據對象的生存週期,將堆分爲新生代和老年代。在新生代中,由於對象生存期短,每次回收都會有大量對象死去,那麼這時就採用複製算法。老年代裏的對象存活率較高,沒有額外的空間進行分配擔保,所以可以使用標記-整理 或者 標記-清除。

15、簡述 java 類加載機制?

虛擬機把描述類的數據從 Class 文件加載到內存,並對數據進行校驗,解析和初始化,最終形成可以被虛擬機直接使用的 java 類型。

16、類加載器雙親委派模型機制?

當一個類收到了類加載請求時,不會自己先去加載這個類,而是將其委派給父類,由父類去加載,如果此時父類不能加載,反饋給子類,由子類去完成類的加載。

17、什麼是類加載器,類加載器有哪些?

實現通過類的權限定名獲取該類的二進制字節流的代碼塊叫做類加載器。
主要有一下四種類加載器:

  1. 啓動類加載器(Bootstrap ClassLoader)用來加載 java 核心類庫,無法被 java 程序直接引用。
  2. 擴展類加載器(extensions class loader):它用來加載 Java 的擴展庫。Java 虛擬機的實現會提供一個擴展庫目錄。該類加載器在此目錄裏面查找並加載 Java 類。
  3. 系統類加載器(system class loader):它根據 Java 應用的類路徑(CLASSPATH)來加載 Java 類。一般來說,Java 應用的類都是由它來完成加載的。可以通過ClassLoader.getSystemClassLoader()來獲取它。
  4. 用戶自定義類加載器,通過繼承 java.lang.ClassLoader 類的方式實現。

18、簡述 java 內存分配與回收策率以及 Minor GC 和 Major GC

  1. 對象優先在堆的 Eden 區分配。
  2. 大對象直接進入老年代.
  3. 長期存活的對象將直接進入老年代. 當 Eden 區沒有足夠的空間進行分配時,虛擬機會執行一次 MinorGC.Minor Gc 通常發生在新生代的 Eden 區,在這個區的對象生存期短,往往發生 Gc 的頻率較高,回收速度比較快;Full Gc/Major GC 發生在老年代,一般情況下,觸發老年代 GC的時候不會觸發 Minor GC,但是通過配置,可以在 Full GC 之前進行一次 MinorGC 這樣可以加快老年代的回收速度。

共同進步,學習分享

歡迎大家關注我的公衆號【風平浪靜如碼】,海量Java相關文章,學習資料都會在裏面更新,整理的資料也會放在裏面。

覺得寫的還不錯的就點個贊,加個關注唄!點關注,不迷路,持續更新!!!

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