JVM知識點總覽:高階Java工程師面試必備

weixin_33843409發表於2018-11-23

下面這篇文章彙集了阿里、美團、Oracle 等大廠的 JVM 考點,你是否能回答得上來?

  1. 什麼是 Java 虛擬機器?為什麼 Java 被稱作是“平臺無關的程式語言”?
  2. Java 程式碼是怎麼執行的?
  3. Java 虛擬機器是如何載入 Java 類的?
  4. JVM 執行記憶體的分類
  5. 如何監控和診斷 JVM 堆內和堆外記憶體使用?
  6. Java 四引用是什麼?
  7. 如何理解 JVM 內建的編譯或 GC 日誌?
  8. JVM 的永久代中會發生垃圾回收麼?
  9. Java 中的兩種異常型別是什麼?他們有什麼區別?
  10. JVM 是如何實現同步的?
  11. Java 內在模型是什麼?
  12. 即使編譯器有哪些優化?
  13. 在什麼情況下重複讀寫操作會被優化?
  14. 什麼樣的垃圾才被回收?
  15. 什麼時候會導致垃圾回收?
  16. 如何利用 JFR 和 JMC 監控 Java 程式?
  17. 如何利用 Unsafe API 繞開 JVM 的控制?
  18. 如何利用位元組碼注入為已有程式碼加料?
  19. ……

我挑選了幾個問題進行解答,希望能對大家面試起到幫助。

1、什麼是 Java 虛擬機器?為什麼 Java 被稱作是“平臺無關的程式語言”?

Java 虛擬機器是一個可以執行 Java 位元組碼的虛擬機器程式。Java 原始檔被編譯成能被 Java 虛擬機器執行的位元組碼檔案。

Java 被設計成允許應用程式可以執行在任意的平臺,而不需要程式設計師為每一個平臺單獨重寫或者是重新編譯。Java 虛擬機器讓這個變為可能,因為它知道底層硬體平臺的指令長度和其他特性。

2、Java 程式碼是怎麼執行的?

這個問題可以分三塊來回答:

  1. 為什麼 Java 要在虛擬機器裡執行?
  2. Java 虛擬機器具體是怎樣執行 Java 位元組碼的?
  3. Java 虛擬機器的執行效率究竟是怎麼樣的?

Java 之所以要在虛擬機器中執行,是因為它提供了可移植性。一旦 Java 程式碼被編譯為 Java 位元組碼,便可以在不同平臺上的 Java 虛擬機器實現上執行。此外,虛擬機器還提供了一個程式碼託管的環境,代替我們處理部分冗長而且容易出錯的事務,例如記憶體管理。

Java 虛擬機器將執行時記憶體區域劃分為五個部分,分別為方法區、堆、PC 暫存器、Java 方法棧和本地方法棧。Java 程式編譯而成的 class 檔案,需要先載入至方法區中,方能在 Java 虛擬機器中執行。

JVM知識點總覽:高階Java工程師面試必備

為了提高執行效率,標準 JDK 中的 HotSpot 虛擬機器採用的是一種混合執行的策略。首先,它會解釋執行 Java 位元組碼,然後會將其中反覆執行的熱點程式碼,以方法為單位進行即時編譯,翻譯成機器碼後直接執行在底層硬體之上。HotSpot 裝載了多個不同的即時編譯器,以便在編譯時間和生成程式碼的執行效率之間做取捨。

JVM知識點總覽:高階Java工程師面試必備

3、Java 虛擬機器是如何載入 Java 類的?

Java 虛擬機器將位元組流轉化為 Java 類的過程,可分為載入、連結以及初始化三大步驟。也可以用蓋房子來類比 Java 虛擬機器中的類載入。

載入是指查詢位元組流,並且據此建立類的過程。以蓋房子為例,村裡的 Tony 要蓋個房子,那麼按照流程他得先找個建築師,跟他說想要設計一個房型,比如說“一房、一廳、四衛”。這裡的房型相當於類,而建築師,就相當於類載入器。村裡有許多建築師,他們等級森嚴,但有著共同的祖師爺,叫啟動類載入器(boot class loader)。

載入需要藉助類載入器,在 Java 虛擬機器中,類載入器使用了雙親委派模型,即接收到載入請求時,會先將請求轉發給父類載入器。

連結,是指將建立成的類合併至 Java 虛擬機器中,使之能夠執行的過程。連結還分驗證、準備和解析三個階段。其中,解析階段為非必須的。

初始化,則是為標記為常量值的欄位賦值,以及執行方法的過程。類的初始化僅會被執行一次,這個特性被用來實現單例的延遲初始化。這放在我們蓋房子的例子中就是,只有當房子裝修過後,Tony 才能真正地住進去。

4、如何監控和診斷 JVM 堆內和堆外記憶體使用?

瞭解 JVM 記憶體的方法有很多,具體能力範圍也有區別,簡單總結如下:

可以使用綜合性的圖形化工具,如 JConsole、VisualVM(注意,從 Oracle JDK 9 開始,VisualVM 已經不再包含在 JDK 安裝包中)等。這些工具具體使用起來相對比較直觀,直接連線到 Java 程式,然後就可以在圖形化介面裡掌握記憶體使用情況。以 JConsole 為例,其記憶體頁面可以顯示常見的堆記憶體和各種堆外部分使用狀態。

也可以使用命令列工具進行執行時查詢,如 jstat 和 jmap 等工具都提供了一些選項,可以檢視堆、方法區等使用資料。

或者,也可以使用 jmap 等提供的命令,生成堆轉儲(Heap Dump)檔案,然後利用 jhat 或 Eclipse MAT 等堆轉儲分析工具進行詳細分析。

如果你使用的是 Tomcat、Weblogic 等 Java EE 伺服器,這些伺服器同樣提供了記憶體管理相關的功能。

另外,從某種程度上來說,GC 日誌等輸出,同樣包含著豐富的資訊。

這裡有一個相對特殊的部分,就是是堆外記憶體中的直接記憶體,前面的工具基本不適用,可以使用 JDK 自帶的 Native Memory Tracking(NMT)特性,它會從 JVM 本地記憶體分配的角度進行解讀。

5、JVM 的永久代中會發生垃圾回收麼?

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

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

6、在 Java 中,物件什麼時候可以被垃圾回收?

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

7、Java 中的兩種異常型別是什麼?他們有什麼區別?

Java 中有兩種異常:受檢查的 (checked) 異常和不受檢查的 (unchecked) 異常。不受檢查的異常不需要在方法或者是建構函式上宣告,就算方法或者是建構函式的執行可能會丟擲這樣的異常,並且不受檢查的異常可以傳播到方法或者是建構函式的外面。相反,受檢查的異常必須要用 throws 語句在方法或者是建構函式上宣告。還有一些 Java 異常處理的小建議,我的專欄裡都有提到。

8、JVM 垃圾回收演算法

標記 - 清除演算法:首先標記出所有需要回收的物件,在標記完成後統一回收所有被標記的物件。

複製演算法:將可用記憶體按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當一塊記憶體用完了,將還存另外一塊上面,然後在把已使用過的記憶體空間一次清理掉。

標記 - 整理演算法:標記過程與“標記 - 清除”演算法一樣,但後續步驟不是直接對可回收物件進行清理,而是讓所一端移動,然後直接清理掉端邊界以外的記憶體。

分代收集演算法:一般是把 Java 堆分為新生代和老年代,根據各個年代的特點採用最適當的收集演算法。新生代都發現有大批物件死去,選用複製演算法。老年代中因為物件存活率高,必須使用“標記 - 清理”或“標記 - 整理”演算法來進行回收。

相關文章