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

“有代碼的地方,就有江湖。”

程序員,就是“一人,一鍵,二機”行走其間的孤獨劍客。我們遊走代碼江湖,彈指間,便可掀起一場風雨變革。而在江湖中狂蕩,必然要練就絕世武功,則需要內外兼備:精妙的招式,加之深厚的內功。武功的基礎是內功,一個內功低的人招式再奇妙,也打不過一個內功深厚之人。同樣兩者也是相輔相成,內功深厚,原來的一招一式威力也會倍增。

對於開發者來說,其道理也是一樣。流行的框架越來越多,封裝也越來越完善,各種框架可以搞定一切。初級程序員只要熟悉基本的使用方法,幾乎不用關注底層的實現,便可以快速地開發上線。但對於想要進階的你來說,更要注重內功,比如算法、設計模式、底層原理等等。只有把基礎打紮實,才能知其然知其所以然,出現Bug能快速發現問題本質。

我在Java虛擬機性能優化方面有着多年的研究,深知Spring全家桶是精妙的招式,JVM就是內功心法很重要的一塊。線上出現性能問題,JVM調優更是不可迴避的問題。但又因Java虛擬機封裝得太好,讓我們幾乎感覺不到它的存在,可學習Java虛擬機對於高級程序員來說,其重要性是不言而喻的。我司在面試高級開發的時候,JVM相關知識也必定是考覈的標準之一。

下面這篇文章集錦了阿里、美團、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. 如何利用字節碼注入爲已有代碼加料?

……

根據我專欄的內容,我挑選了幾個問題進行解答,希望能對大家面試起到幫助。

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堆分爲新生代和老年代,根據各個年代的特點採用最適當的收集算法。新生代都發現有大批對象死去,選用複製算法。老年代中因爲對象存活率高,必須使用“標記-清理”或“標記-整理”算法來進行回收。

相關文章