每一個Java 開發人員都知道字節碼由JRE (Java運行時環境)執行。但許多人不知道JRE是Java虛擬機(JVM)的實現, 它負責分析字節碼、解析並執行代碼。作為一個開發人員瞭解JVM架構是非常重要的,因為它使我們能更高效的編寫代碼。在這篇文章中我們將更深入瞭解Java中的JVM架構以及JVM的各個組件。
那麼JVM到底是什麼呢?
虛擬機 是物理機器的一個軟件實現。Java運行在VM上,實現WORA (一處編寫,處處運行)。 編譯器將Java文件編譯成Java .class 文件,然後這個.class文件被輸入到JVM中進行類文件的加載和執行。下面是一個JVM的架構圖。
JVM是如何工作的呢?
正如上面的架構圖所示,JVM被分為三個主要的子系統:
類加載器子系統
運行時數據區
執行引擎
1. 類加載器子系統
Java的動態類加載功能是由類加載器子系統處理。當它在運行時(不是編譯時)首次引用一個類時,它加載、鏈接並初始化該類文件。
1.1 加載
類由此組件加載。啟動類加載器 (Boot Strap class Loader)、擴展類加載器(Extension class Loader)和應用程序類加載器(Application class Loader) 這三種類加載器幫助完成類的加載。
啟動類加載器 – 負責從啟動類路徑中加載類,無非就是rt.jar。這個加載器會被賦予最高優先級。
擴展類加載器 – 負責加載ext 目錄(jre\\lib)內的類.
應用程序類加載器 – 負責加載應用程序級別類路徑,涉及到路徑的環境變量等etc.
上述的類加載器會遵循委託層次算法(Delegation Hierarchy Algorithm)加載類文件。
1.2 鏈接
校驗 – 字節碼校驗器會校驗生成的字節碼是否正確,如果校驗失敗,我們會得到校驗錯誤。
準備 – 分配內存並初始化默認值給所有的靜態變量。
解析 – 所有符號內存引用被方法區(Method Area)的原始引用所替代。
1.3 初始化
這是類加載的最後階段,這裡所有的靜態變量會被賦初始值, 並且靜態塊將被執行。
2. 運行時數據區(Runtime Data Area)
運行時數據區域被劃分為5個主要組件:
方法區(Method Area) – 所有類級別數據將被存儲在這裡,包括靜態變量。每個JVM只有一個方法區,它是一個共享的資源。
堆區(Heap Area)– 所有的對象和它們相應的實例變量以及數組將被存儲在這裡。每個JVM同樣只有一個堆區。由於方法區和堆區的內存由多個線程共享,所以存儲的數據不是線程安全的。
棧區(Stack Area)– 對每個線程會單獨創建一個運行時棧。對每個函數呼叫會在棧內存生成一個棧幀(Stack Frame)。所有的局部變量將在棧內存中創建。棧區是線程安全的,因為它不是一個共享資源。棧幀被分為三個子實體:
1.局部變量數組– 包含多少個與方法相關的局部變量並且相應的值將被存儲在這裡。
2.操作數棧– 如果需要執行任何中間操作,操作數棧作為運行時工作區去執行指令。
3.幀數據– 方法的所有符號都保存在這裡。在任意異常的情況下,catch塊的信息將會被保存在幀數據裡面。
4.PC寄存器– 每個線程都有一個單獨的PC寄存器來保存當前執行指令的地址,一旦該指令被執行,pc寄存器會被更新至下條指令的地址。
5.本地方法棧– 本地方法棧保存本地方法信息。對每一個線程,將創建一個單獨的本地方法棧。
3. 執行引擎
分配給運行時數據區的字節碼將由執行引擎執行。執行引擎讀取字節碼並逐段執行。
解釋器– 解釋器能快速的解釋字節碼,但執行卻很慢。 解釋器的缺點就是,當一個方法被調用多次,每次都需要重新解釋。
JIT 編譯器– JIT編譯器消除了解釋器的缺點。執行引擎利用解釋器轉換字節碼,但如果是重複的代碼則使用JIT編譯器將全部字節碼編譯成本機代碼。本機代碼將直接用於重複的方法調用,這提高了系統的性能。
1.中間代碼生成器– 生成中間代碼
2.代碼優化器– 負責優化上面生成的中間代碼
3.目標代碼生成器– 負責生成機器代碼或本機代碼
4.探測器(Profiler)– 一個特殊的組件,負責尋找被多次調用的方法。
3.垃圾回收器: 收集並刪除未引用的對象。可以通過調用"System.gc()"來觸發垃圾回收,但並不保證會確實進行垃圾回收。JVM的垃圾回收只收集哪些由new關鍵字創建的對象。所以,如果不是用new創建的對象,你可以使用finalize函數來執行清理。
此處敲黑板
關注+轉發 私信“Java”獲取面試資料
關注+轉發 私信“Java”獲取面試資料
關注+轉發 私信“Java”獲取面試資料
JVM內存模型
由顏色可以看出,jdk1.8之前,堆內存被分為新生代,老年代,永久帶,jdk1.8及以後堆內存被分成了新生代和老年代。新生代的區域又分為eden區,s0區,s1區,默認比例是8:1:1,元空間可以理解為直接的物理內存
程序員到底要不要學習JVM
總有人問這個東西好像用不上,於是要不要學這樣的問題。
然後又總有人擔心一直搬磚成天做些重複沒提升的東西。
如果你這輩子只甘心做一個平庸的Java碼農,那麼你完全沒有必要去學習JVM相關的知識,學習JVM對於一個Java程序員的好處大概可以概括為下幾點:
1.你能夠明白為什麼Java最早期被稱為解釋型語言,而後來為什麼又被大家叫做解釋與編譯並存的語言(瞭解JVM中解釋器以及即時編譯器就可以回答這個問題);
2.你能夠理解動態編譯與靜態編譯的區別,以及動態編譯相對於靜態編譯到底有什麼好處(JVM JIT);
3.你能夠利用一些工具,jmap, jvisualvm, jstat, jconsole等工具可以輔助你觀察Java應用在運行時堆的佈局情況,由此你可以通過調整JVM相關參數提高Java應用的性能;
4.可以清楚知道Java程序是如何執行的;
5.可以明白為什麼Java等高級語言具有可移植性強的特性。
其實這個問題相當於“為什麼C/C++程序員需要學體系結構與編譯原理?”
JVM暫時總結到這裡,下次和大家掰扯掰扯JVM如何調優,先放張思維導圖
BATJ關於JVM面試題
再次敲黑板
需要面試資料
關注+轉發 私信“Java”免費獲取
閱讀更多 即將禿頭的Java程序員 的文章