在Java開發領域,JVM面試一直是一個熱門話題。作為一名優秀的開發者,你是否已經準備好迎接這場挑戰了呢?今天,我們就來深度解析一下JVM面試的熱點問題,幫助你更好地應對面試,一舉拿下offer!
1、說一下 JVM 的主要組成部分及其作用?
JVM包含兩個子系統和兩個組件,兩個子系統為Class loader(類裝載)、Execution engine(執行引擎);兩個組件為Runtime data area(運行時數據區)、Native Interface(本地接口)。
- Class loader(類裝載):根據給定的全限定名類名(如:java.lang.Object)來裝載class文件到 Runtime data area中的method area。
- Execution engine(執行引擎):執行classes中的指令。
- Native Interface(本地接口):與native libraries交互,是其它編程語言交互的接口。
- Runtime data area(運行時數據區域):這就是我們常說的JVM的內存。
作用:
在Java程序的執行過程中,首先會通過編譯器將Java源代碼轉換成一種叫做字節碼(Bytecode)的中間形式。這種字節碼是一種平臺無關的二進制代碼,它比Java源代碼更接近于機器語言,但仍然包含了一些用于定位和操作數據結構的指令。
然后,類加載器(ClassLoader)會負責將這種字節碼加載到內存中。這個過程通常是動態進行的,也就是說,當程序需要使用某個類的時候,類加載器就會將這個類的字節碼加載到內存中。這個過程通常發生在Java虛擬機(JVM)的運行時數據區(Runtime Data Area)的方法區內。
需要注意的是,雖然字節碼文件是JVM的一套指令集規范,但它并不能直接交給底層操作系統去執行。這是因為不同的操作系統可能對同一組字節碼有不同的解釋方式,因此需要一個專門的命令解析器執行引擎(Execution Engine)來將字節碼翻譯成底層系統可以執行的指令。
最后,Java程序中還可以調用其他語言編寫的本地庫接口(Native Interface),進行一些系統調用或者C函數調用。這種方式可以使Java程序更好地利用底層系統的功能,提高程序的性能和效率。
下面是ava程序運行機制詳細說明
Java程序運行機制步驟:
- 首先利用IDE集成開發工具編寫Java源代碼,源文件的后綴為.java;
- 再利用編譯器(javac命令)將源代碼編譯成字節碼文件,字節碼文件的后綴名為.class;
- 運行字節碼的工作是由解釋器(java命令)來完成的。
從上圖中可以觀察到,Java文件經過編譯器的處理后,被轉換成了對應的.class文件。接下來,類加載器會負責將這些.class文件加載到Java虛擬機(JVM)中。
實際上,類的加載過程可以用一句話來概括:類的加載是將類的二進制數據從.class文件中讀取出來,并將其放入內存中的運行時數據區的方法區內。然后,在堆區創建一個java.lang.Class對象,用于封裝類在方法區內的數據結構。
通過類加載器的作用,JVM能夠動態地加載和卸載類,使得程序可以在運行時動態地獲取和使用類。這種動態性是Java語言的一大特點,它賦予了程序更高的靈活性和擴展性。
2、說一下JVM運行時數據區?
Java虛擬機在執行Java程序的過程中,會將其管理的內存區域劃分為多個不同的數據區域。每個數據區域都有特定的用途,并且它們的創建和銷毀時間也不同。
首先,Java虛擬機將內存區域劃分為堆(Heap)、方法區(Method Area)和棧(Stack)。
-
堆:堆是Java虛擬機所管理的最大的數據區域。它用于存儲對象實例以及數組。當創建一個新的對象時,會在堆上分配相應的內存空間。而當一個對象不再被引用時,垃圾回收器會負責將其所占用的內存釋放回堆中。堆的大小可以通過JVM的參數進行設置,例如-Xmx和-Xms分別表示最大堆大小和初始堆大小。
-
方法區:方法區是用于存儲類信息、常量池、靜態變量等數據的一塊內存區域。它與Java類相關聯,因為每個Java類都包含方法區的一份拷貝。方法區的生命周期與Java虛擬機的生命周期一致,因此隨著虛擬機進程的啟動而存在。
-
棧:棧是Java虛擬機用來支持函數調用和方法執行的數據結構。每個線程在創建時都會創建一個獨立的棧,其中存儲著局部變量、操作數棧和返回地址等信息。棧的特點是后進先出(LIFO),即最后進入棧的元素會最先被取出。棧的生命周期與線程的生命周期一致,因此依賴于線程的啟動和結束來建立和銷毀。
除了這些主要的數據區域外,Java虛擬機還可能劃分其他一些輔助的區域,例如程序計數器(Program Counter Register)和本地方法棧(Native Method Stack)等。這些區域的具體用途和生命周期取決于Java虛擬機的實現和運行環境。
- 程序計數器(Program Counter Register):當前線程所執行的字節碼的行號指示器,字節碼解析器的工作是通過改變這個計數器的值,來選取下一條需要執行的字節碼指令,分支、循環、跳轉、異常處理、線程恢復等基礎功能,都需要依賴這個計數器來完成;
- 本地方法棧(Native Method Stack):與虛擬機棧的作用是一樣的,只不過虛擬機棧是服務 Java方法的,而本地方法棧是為虛擬機調用 Native 方法服務的;
3、什么是堆內存?
以Hotspot為例,堆內存(HEAP)主要由GC模塊進行分配和管理, 可分為以下部分:
- 新生代(伊甸園區+幸存者區)
- 老年代
我們在jvm參數中只要使用-Xms,-Xmx等參數就可以設置堆的大小和最大值,理解jvm的堆還需要知道下面這個公式:
堆內內存 = 新生代+老年代。如下面的圖所示:
在使用堆內內存(on-heap memory)的時候,完全遵守JVM虛擬機的內存管理機制,采用垃圾回收器(GC)統一進行內存管理,GC會在某些特定的時間點進行一次徹底回收,也就是Full GC,GC會對所有分配的堆內內存進行掃描,在這個過程中會對JAVA應用程序的性能造成一定影響,還可能會產生Stop The World。
常見的垃圾回收算法主要有:
- 引用計數器法(Reference Counting)
- 標記清除法(Mark-Sweep)
- 復制算法(Coping)
- 標記壓縮法(Mark-Compact)
- 分代算法(Generational Collecting)
4、什么是堆外內存?
堆外內存,也常被稱為直接內存,是Java虛擬機管理內存的一種方式。與Java虛擬機的堆內存相對應,堆外內存是將內存對象分配在Java虛擬機的堆以外的內存區域。這部分內存并不受Java虛擬機的管理,而是直接由操作系統進行操作和管理。
這種設計的主要優點是能夠在一定程度上減少垃圾回收對應用程序造成的影響。因為堆外內存的分配和釋放不依賴于Java虛擬機的垃圾回收機制,所以它可以更快速地進行內存的分配和釋放,從而提高應用程序的性能。
作為Java開發者,我們經常使用java.nio.DirectByteBuffer類來管理堆外內存。這個類的實例會在對象創建的時候自動分配堆外內存。DirectByteBuffer類提供了一種在Java堆外分配內存的方式,它主要是通過其成員變量unsafe來進行操作的。
5、使用堆外內存的優點
- 減少了垃圾回收, 因為垃圾回收會暫停其他的工作。
- 加快了復制的速度,堆內在flush到遠程時,會先復制到直接內存(非堆內存),然后在發送;而堆外內存相當于省略掉了這個工作。
6、簡述Java垃圾回收機制
在Java編程中,程序員并不需要顯式地釋放對象的內存,這是由Java虛擬機(JVM)自動完成的。當一個對象不再被任何變量引用時,它就會被視為“垃圾”,并被標記為可回收的內存。然后,JVM會定期運行垃圾回收線程來檢查這些垃圾對象,并將它們從內存中清除。
這個垃圾回收線程在JVM中的優先級是低的,這意味著它不會在程序運行過程中頻繁地執行。相反,它會在JVM認為合適的時候執行。例如,當JVM的空閑時間超過一定閾值時,或者當堆內存的使用率達到一定限制時,垃圾回收線程就會被觸發。
垃圾回收線程的工作過程是這樣的:首先,它會掃描所有的對象,找出那些沒有被任何其他變量引用的對象。這些對象就被稱為“垃圾”。然后,它將這些垃圾對象添加到一個稱為“垃圾回收集合”的數據結構中。最后,它會從內存中徹底清除這些垃圾對象,釋放它們占用的內存空間。
總的來說,Java程序員不需要擔心內存管理問題,因為JVM會自動處理這些問題。這大大簡化了編程的復雜性,使得程序員可以更專注于實現具體的功能,而不是管理內存。
7、哪些對象會被存放到老年代?
- 新生代對象每次經歷?次minor gc,年齡會加1,當達到年齡閾值(默認為15歲)會直接進?老年代;
- 大對象直接進?老年代;
- 新生代復制算法需要?個survivor區進行輪換備份,如果出現大量對象在minor gc后仍然存活的情況時,就需要老年代進行分配擔保,讓survivor?法容納的對象直接進?老年代;
- 如果在Survivor空間中相同年齡所有對象大?的總和大于Survivor空間的?半,年齡大于或等于該年齡的對象就可以直接進?年?代。
8、什么時候觸發full gc?
- 調用System.gc時,系統建議執行Full GC,但是不必然執行
- 老年代空間不?
- 方法區空間不?
- 通過Minor GC后進入老年代的平均大小大于老年代的可用內存
- 由Eden區、From Space區向To Space區復制時,對象大小大于To Space可用內存,則把該對象轉存到老年代,且老年代的可用內存小于該對象大小