詳解JVM中的內存模型是什么?


強烈推薦

不管是找工作還是提升水平,都建議讀一下《深入理解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內存


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM