詳解JVM中的記憶體模型是什麼?

程式設計開發分享者發表於2020-09-11

強烈推薦

不管是找工作還是提升水平,都建議讀一下《深入理解Java虛擬機器》這本書,詳細講解了JVM中的記憶體管理、類載入過程、垃圾回收以及最重要的效能調優實戰。
本部落格也是參考了這本書,有不對的地方還請指正。在這裡給大家準備了電子版和視訊教程,詳情請掃右側公告欄中二維碼後,回覆【java虛擬機器】獲得。

一、快速掃盲

1. JVM是什麼

  JVM是Java Virtual Machine的縮寫,即我們們經常提到的Java虛擬機器。虛擬機器是一種抽象化的計算機,有著自己完善的硬體架構,如處理器、堆疊等,具體有什麼我們們不做了解。目前我們只需要知道想要執行Java檔案,必須先通過一個叫javac的編譯器,將程式碼編譯成class檔案,然後通過JVM把class檔案解釋成各個平臺可以識別的機器碼,最終實現跨平臺執行程式碼。
image.png

2. JDK、JRE、JVM之間的關係

  • JDK:全稱為Java Development Kit,漢語為java開發工具包,即所有有關java的東西都包含在裡面,比如執行環境JRE、java的核心程式碼、JVM等等。
  • JRE:全稱為Java Runtime Environment,漢語為java執行環境,即想要執行java檔案必須先有java的環境才行,jre就是提供了這麼一個環境。
  • JVM:上面已經提到了JVM,它是java最核心的部分。
    簡單用一張圖來理解這三個的關係:
    圖一:JDK、JRE、JVM之間的關係

3. jvm的組成成分

圖二:JVM的組成圖
  不瞭解jvm的同學看到這張圖後可能會有點懵逼,不過沒關係,放這張圖只是想讓你瞭解jvm中有三塊內容非常重要,1.java程式碼如何執行?2.記憶體如何管理?3.執行緒資源如何利用?腦袋裡有個印象即可,帶著問題去學習。文章最下方有本部落格參考書和視訊下載地址。

4. 執行java檔案的大概流程

  想要執行java的原始檔,必須要經過javac編譯器編譯成.class檔案,也就是位元組碼檔案。然後通過jvm中的直譯器,解釋成特定機器上的機器碼。每種機器上的直譯器是不一樣的,我們經常用的也就是windows和linux系統,這也是為什麼java能夠跨平臺的原因。當一個程式從開始執行,虛擬機器就開始例項化,多個程式執行就會存在多個虛擬機器例項,程式退出或者關閉,虛擬機器例項也將隨之消亡,多個虛擬機器之間的資料是不共享的。

二、JVM執行時資料區

1. 執行時資料區域組成

  虛擬機器在執行java程式時,會將自己管理的記憶體劃分為幾個區域,每個區域都有自己的用途,並且建立時間和銷燬時間也不一樣。在程式執行時的記憶體區域主要可以劃分為五個,分別是:方法區、堆、虛擬機器棧、本地方法棧、程式計數器。可以用下面的圖來描述:
Java虛擬機器執行時資料區域

2. Java堆

  Java堆是java虛擬機器所管理的記憶體中最大的一塊,是被所有執行緒都共享的記憶體區域。存在的唯一目的就是存放物件例項,幾乎所有的物件例項都在這裡進行分配記憶體。不過目前隨著技術的不斷髮展,也並不是所有的物件例項都在堆中分配記憶體,可能也存在棧上分配。由於所佔空間大,又存放各種例項物件,因此java虛擬機器的垃圾回收機制主要管理的就是此區域,詳細的垃圾回收方法以後會提到。JVM規範中規定堆可以處於物理上不連續的記憶體空間中,只要邏輯上是連續的即可。並且可以通過-Xmx和-Xms來擴充套件堆的記憶體大小,如果在堆中沒有足夠的記憶體為例項分配,並且堆也無法在擴充套件時,就會報OutOfMemoryError異常。

3 方法區

跟Java堆一樣,方法區是各個執行緒共享的記憶體區域,此區域是用來儲存類的資訊(類的名稱、欄位資訊、方法資訊)、靜態變數、常量以及編譯器編譯後的程式碼。JVM規範中並不區分方法區和堆,只把方法區描述為堆的邏輯部分,但是它卻有一個別名叫做非堆(Non-Heap),目的就是與Java堆區分開。根據垃圾回收機制中分代回收的思想,如果在HotSpot虛擬機器上開發,可以把方法區稱為“永久代”(只是可以這麼理解,但實質是不一樣的),垃圾回收機制在Java堆中劃分一個部分稱為永久代,用此區域來實現方法區,這樣HotSpot的垃圾收集器就可以像管理Java堆一樣管理這部分記憶體,而不必為方法區開發專門的記憶體管理器。

執行時常量池

執行時常量池是方法區的一個部分,class檔案中除了有類的版本、欄位、方法、介面等描述資訊外,還有一項資訊是常量池,用於存放編譯期間生成的各種字面量和符號引用,這部分內容會在類載入後進入方法區的執行時常量池中。Java 虛擬機器對 Class 檔案的每一部分(自然也包括常量池)的格式都有嚴格的規定,每一個位元組用於儲存哪種資料都必須符合規範上的要求,這樣才會被虛擬機器認可、裝載和執行。

4. 程式計數器

雖然在上圖中程式計數器的面積很大,但實際上它是一塊較小的記憶體空間,可以看做當前執行緒所執行位元組碼的行號指示器。位元組碼直譯器在工作中時下一步該幹啥、到哪了,就是通過它來確定的。大家都知道在多執行緒的情況下,CPU在執行執行緒時是通過輪流切換執行緒實現的,也就是說一個CPU處理器(假設是單核)都只會執行一條執行緒中的指令,因此為了執行緒切換後能恢復到正確的執行位置,每個執行緒都要有一個獨立的程式計數器,各條執行緒之間的計數器互不影響,獨立儲存,我們稱這類記憶體區域為“執行緒私有”的記憶體。很明顯,程式計數器就是執行緒私有的。如果執行緒正在執行的是一個java方法,程式計數器記錄的是正在執行的虛擬機器位元組碼指令地址;如果執行的Native方法,程式計數器記錄的值為空(Undefined),此記憶體區域是java中唯一一個在java虛擬機器規範中沒有規定任何OutOfMemoryError情況的區域。

5. Java虛擬機器棧

我們經常會把java記憶體粗糙的分為兩個部分,堆和棧,Java虛擬機器棧就是棧這一部分,或者說是虛擬機器棧中區域性變數表部分。跟程式計數器一樣,虛擬機器棧也是執行緒私有的,它的生命週期跟執行緒相同。每個方法在執行的同時都會建立一個棧幀(Stack Frame),每個棧幀對應一個被呼叫的方法,棧幀中用於儲存區域性變數表、運算元棧、動態連結串列、方法出口等資訊。每一個方法從開始執行到結束就對應著一個棧幀在虛擬機器棧中從入棧到出棧的過程。

  • 區域性變數表:顧名思義,他就是用來儲存方法中的區域性變數(包括在方法中生命的非靜態變數以及函式形參),對於基本資料型別,直接存值,對於引用型別的變數,儲存指向該物件的引用。由於它只存放基本資料型別的變數、引用型別的地址和返回值的地址,這些型別所需空間大小已知且固定,所以當進入一個方法時,這個方法需要在棧幀中分配多大的區域性變數空間是完全可以確定的,在方法執行期間也不會改變區域性變數表的大小。
  • 指向執行常量池的引用:在方法執行過程中難免會使用到類中定義的常量,因此棧幀中要存放一個指向執行時常量池的引用。
  • 方法返回地址:當一個方法執行結束後,要返回到之前呼叫它的地方,因此在棧幀中需要儲存一個方法返回地址。

6. 本地方法棧

本地方法棧與虛擬機器棧的功能非常的相似,區別不過是虛擬機器棧為虛擬機器執行java方法服務,而本地方法棧為虛擬機器執行Native方法服務。有的虛擬機器並不會區分本地方法棧和虛擬機器棧,比如Sun HotSpot虛擬機器直接將兩個合二為一。

7. 用一張圖總結

JVM記憶體總結

相關文章