1. 程式人生 > 其它 >JVM 記憶體區域

JVM 記憶體區域

JVM 在執行程式碼時,必須使用多塊記憶體空間的,不同的記憶體空間用來存放不同的資料,再配合程式碼流程,才能讓系統運轉。

一、記憶體區域劃分

程式碼執行時,需要分配記憶體用來存放類;執行方法、變數時,也需要存放在記憶體;建立物件時,也需要放在記憶體。

二、方法區

位元組碼檔案(.class)載入進來的類,以及一些類似常量池的東西會放在這個區域,java 1.8 之前叫 “方法區”,之後叫 “元資料空間(Metaspace)”。

三、程式計數器

實際上 java 編譯後的位元組碼檔案才是計算機真正理解的語言。使用 idea 等工具可以檢視編譯後的位元組碼檔案,大概就是這個樣子。

比如 “0: aload_0” 這樣的就是 “位元組碼指令”,計算機讀到這種指令才知道執行。

所以,Java 程式碼通過 JVM 跑起來做的第一件事就是,把 Java 程式碼翻譯成各種位元組碼指令,然後通過位元組碼執行引擎,位元組碼會被一條一條的執行。

在執行指令時,JVM 需要一個特殊的記憶體區域 “程式計數器”,用來記錄當前執行的位元組碼指令的位置。

實際上,在多執行緒情況下,甚至是執行 main 方法的主執行緒都有專屬的記憶體空間和專屬的程式計數器,這樣每個執行緒都能知道當前執行到那一條指令了。

四、Java 虛擬機器棧

在執行程式碼指令時,會有程式計數器來記錄當前指令位置。

而一些方法內的區域性變數需要 JVM 分配一塊區域來儲存資料,這就是 Java 虛擬機器棧,每個執行緒都有專屬的虛擬機器棧。

一個執行緒執行了一個方法,就會對這個方法呼叫建立一個對應的棧幀,棧幀內有這個方法的區域性變量表、運算元棧、動態連結、方法出口等。

執行緒在執行到某個方法時就會建立一個棧幀壓入自己的 Java 虛擬機器棧中去,然後棧幀的區域性變量表中就會存放這個方法內的區域性變數。

“Java 虛擬機器棧幀” 的作用就是:呼叫執行任何方法時,都會給方法建立棧幀,然後入棧,棧幀裡存放方法執行的相關資訊以及區域性變數,方法執行完之後就出棧。

五、Java 堆記憶體

JVM 中另外一個非常關鍵的區域就是 Java 堆記憶體,這裡存放的就是程式碼中建立的各種物件。

然後在執行方法的棧幀裡的區域性變量表裡會有一個變數指向堆記憶體裡的物件。一個物件對記憶體空間的佔用,大致分兩塊:

  • 一個是物件本身的資訊
  • 一個是物件例項變數佔用的空間

比如物件頭,如果在64位的 linux 作業系統上,會佔用16位元組,

如果你的例項物件內部有個 int 型別的例項變數,他會佔用4個位元組,

如果是 long 型別的例項變數,會佔用8個位元組。

如果是陣列、Map之類的,那麼就會佔用更多的記憶體了。

另外JVM對這塊有很多優化的地方,比如補齊機制、指標壓縮機制

六、其他記憶體區域

在某些原始碼內可以看到用 native 修飾的一些方法的引用。

這些方法都是去呼叫本地作業系統的一些方法,用 c 語言寫的或者一些類庫。

這種本地方法棧跟 Java 虛擬機器棧類似,存放 native 方法的區域性變量表之類的資訊。

還有一個區域,是不屬於JVM的,通過NIO中的allocateDirect這種API,可以在Java堆外分配記憶體空間。

然後,通過Java虛擬機器裡的DirectByteBuffer來引用和操作堆外記憶體空間。

這種堆外分配記憶體可以提升效能。

七、流程總結

首先,JVM 程序啟動時,會載入 -> 驗證 -> 準備 -> 解析 -> 初始化,

這時 Java 程式碼會被編譯為位元組碼檔案(.class),

然後會使用類載入器通過雙親委派機制載入核心類,擴充套件類和應用程式類到 JVM 方法區。

隨後 JVM 啟動一個 main 執行緒,並分配執行緒的棧記憶體空間,

執行 main 方法時,會建立 main 方法的棧幀並壓入棧。

然後位元組碼執行引擎執行 main 方法內的指令時,程式計數器會記錄執行緒執行的指令位置。

其中方法內建立的物件會被存放在堆記憶體,被區域性變數引用其地址。

方法執行完,棧幀就會從對應的虛擬機器棧裡出棧,main 棧幀出棧也就意味著 JVM 程序結束了。