經典面試題|講一講JVM的組成

王磊的部落格發表於2019-04-09

JVM(Java 虛擬機器)算是面試必問的問題的了,而但凡問 JVM 一定會問的第一個問題就是:講一講 JVM 的組成?那本文就注重講一下 JVM 的組成。

首先來說 JVM 的組成分為,整體組成部分和執行時資料區組成部分,一般開發者關注的和麵試官問的都是後者,但本文會詳細講解以上兩個組成部分。

一、JVM 整體組成

JVM 整體組成可分為以下四個部分:

  1. 類載入器(ClassLoader)

  2. 執行時資料區(Runtime Data Area)

  3. 執行引擎(Execution Engine)

  4. 本地庫介面(Native Interface)

各個組成部分的用途:

程式在執行之前先要把java程式碼轉換成位元組碼(class檔案),jvm首先需要把位元組碼通過一定的方式 類載入器(ClassLoader) 把檔案載入到記憶體中 執行時資料區(Runtime Data Area) ,而位元組碼檔案是jvm的一套指令集規範,並不能直接交個底層作業系統去執行,因此需要特定的命令解析器 執行引擎(Execution Engine) 將位元組碼翻譯成底層系統指令再交由CPU去執行,而這個過程中需要呼叫其他語言的介面 本地庫介面(Native Interface)來實現整個程式的功能,這就是這4個主要組成部分的職責與功能。

而我們通常所說的jvm組成指的是執行時資料區(Runtime Data Area),因為通常需要程式設計師除錯分析的區域就是“執行時資料區”,或者更具體的來說就是“執行時資料區”裡面的Heap(堆)模組,那接下來我們來看執行時資料區(Runtime Data Area)是由哪些模組組成的。

二、執行時資料區組成

jvm的執行時資料區,不同虛擬機器實現可能略微有所不同,但都會遵從Java虛擬機器規範,Java 8 虛擬機器規範規定,Java虛擬機器所管理的記憶體將會包括以下幾個執行時資料區域:

  1. 程式計數器(Program Counter Register)

  2. Java虛擬機器棧(Java Virtual Machine Stacks)

  3. 本地方法棧(Native Method Stack)

  4. Java堆(Java Heap)

  5. 方法區(Methed Area)

接下來我們分別介紹每個區域的用途。

①、Java程式計數器

程式計數器(Program Counter Register)是一塊較小的記憶體空間,它可以看作是當前執行緒所執行的位元組碼的行號指示器。在虛擬機器的概念模型裡,位元組碼解析器的工作是通過改變這個計數器的值來選取下一條需要執行的位元組碼指令,分支、迴圈、跳轉、異常處理、執行緒恢復等基礎功能都需要依賴這個計數器來完成。

特性:記憶體私有

由於jvm的多執行緒是通過執行緒輪流切換並分配處理器執行時間的方式來實現的,也就是任何時刻,一個處理器(或者說一個核心)都只會執行一條執行緒中的指令。因此為了執行緒切換後能恢復到正確的執行位置,每個執行緒都有獨立的程式計數器。

異常規定:無

如果執行緒正在執行Java中的方法,程式計數器記錄的就是正在執行虛擬機器位元組碼指令的地址,如果是Native方法,這個計數器就為空(undefined),因此該記憶體區域是唯一一個在Java虛擬機器規範中沒有規定OutOfMemoryError的區域。

②、Java虛擬機器棧

Java虛擬機器棧(Java Virtual Machine Stacks)描述的是Java方法執行的記憶體模型,每個方法在執行的同時都會建立一個線幀(Stack Frame)用於儲存區域性變數表、運算元棧、動態連結、方法出口等資訊,每個方法從呼叫直至執行完成的過程,都對應著一個線幀在虛擬機器棧中入棧到出棧的過程。

特性:記憶體私有,它的生命週期和執行緒相同。

異常規定:StackOverflowError、OutOfMemoryError

1、如果執行緒請求的棧深度大於虛擬機器所允許的棧深度就會丟擲StackOverflowError異常。

2、如果虛擬機器是可以動態擴充套件的,如果擴充套件時無法申請到足夠的記憶體就會丟擲OutOfMemoryError異常。

③、本地方法棧

本地方法棧(Native Method Stack)與虛擬機器棧的作用是一樣的,只不過虛擬機器棧是服務Java方法的,而本地方法棧是為虛擬機器呼叫Native方法服務的。

在Java虛擬機器規範中對於本地方法棧沒有特殊的要求,虛擬機器可以自由的實現它,因此在Sun HotSpot虛擬機器直接把本地方法棧和虛擬機器棧合二為一了。

特性和異常: 同虛擬機器棧,請參考上面知識點。

④、Java堆

Java堆(Java Heap)是Java虛擬機器中記憶體最大的一塊,是被所有執行緒共享的,在虛擬機器啟動時候建立,Java堆唯一的目的就是存放物件例項,幾乎所有的物件例項都在這裡分配記憶體,隨著JIT編譯器的發展和逃逸分析技術的逐漸成熟,棧上分配、標量替換優化的技術將會導致一些微妙的變化,所有的物件都分配在堆上漸漸變得不那麼“絕對”了。

特性:記憶體共享

異常規定:OutOfMemoryError

如果在堆中沒有記憶體完成例項分配,並且堆不可以再擴充套件時,將會丟擲OutOfMemoryError。

Java虛擬機器規範規定,Java堆可以處在物理上不連續的記憶體空間中,只要邏輯上連續即可,就像我們的磁碟空間一樣。在實現上也可以是固定大小的,也可以是可擴充套件的,不過當前主流的虛擬機器都是可擴充套件的,通過-Xmx和-Xms控制。

⑤、方法區

方法區(Methed Area)用於儲存已被虛擬機器載入的類資訊、常量、靜態變數、即時編譯後的程式碼等資料。

誤區:方法區不等於永生代

很多人原因把方法區稱作“永久代”(Permanent Generation),本質上兩者並不等價,只是HotSpot虛擬機器垃圾回收器團隊把GC分代收集擴充套件到了方法區,或者說是用來永久代來實現方法區而已,這樣能省去專門為方法區編寫記憶體管理的程式碼,但是在Jdk8也移除了“永久代”,使用Native Memory來實現方法區。

特性:記憶體共享

異常規定:OutOfMemoryError

當方法無法滿足記憶體分配需求時會丟擲OutOfMemoryError異常。

三、擴充套件知識

本節將擴充套件一些和記憶體分配有關的知識。

執行時常量池

執行時常量池是方法區的一部分,Class檔案中除了有類的版本、欄位、方法、介面等描述資訊外,還有一項資訊是常量池(Constant Pool Table)用於存放編譯期生成的各種字面量和符號引用,這部分在類載入後進入方法區的執行是常量池中,如String類的intern()方法。

直接記憶體

直接記憶體(Direct Memory)並不是虛擬機器執行時資料區的一部分,但這部分記憶體也會被頻繁的使用,而且可能導致OutOfMemoryError。在JDK 1.4中新加入了NIO類,引入了一種基於Channel與緩衝區Buffer的IO方式,它通過一個儲存在Java堆中的DirectByteBuffer物件作為這塊記憶體的引用操作,它因此更高效,它避免了Java堆和Native堆來回交換資料的時間。

注意 :直接記憶體分配不會受到Java堆大小的限制,但是受到本機總記憶體大小限制,在設定虛擬機器引數的時候,不能忽略直接記憶體,把實際記憶體設定為-Xmx,使得記憶體區域的總和大於實體記憶體的限制,從而導致動態擴充套件時出現OutOfMemoryError異常。

四、總結

本文講了jvm的主要組成部分,以及組成部分中最重要的執行時資料區(Runtime Data Area)的構成,其中程式計數器、虛擬機器棧和本地方法為私有記憶體,會隨著執行緒而生,隨著執行緒而滅,而Java堆作為最大的記憶體區域將是開發人員重點關注的記憶體區域,還有方法區以及執行時常量區與永生代的關係,最後講了直接記憶體的實現過程已經使用時需要主要的點,希望能夠幫助大家更好的理解jvm。

五、參考資料

《深入理解Java虛擬機器》

【End】

關注下方二維碼,訂閱更多精彩內容。

經典面試題|講一講JVM的組成


相關文章