Java 虛擬機器執行時資料區詳解
阿新 • • 發佈:2020-11-29
> 本文摘自深入理解 Java 虛擬機器第三版
## 概述 Java 虛擬機器在執行 Java 程式的過程中會把它所管理的記憶體劃分為若干個不同的資料區域,這些區域有各自的用途,以及建立和銷燬的時間,有的區域隨著虛擬機器程序的啟動而一直存在,有的區域則是依賴使用者執行緒的啟動和結束而建立和銷燬。因此,我們可以根據這個特點將區域劃為為執行緒公有區域和執行緒私有區域兩部分 ![](https://img2020.cnblogs.com/blog/1759254/202011/1759254-20201129150431161-2112191873.png)
## 程式計數器 程式計數器(Program Counter Register)是一塊較小的記憶體空間,可以看作是當前執行緒所執行的位元組碼的行號指示器。位元組碼直譯器通過改變計數器的值來選取下一條需要執行的位元組碼指令,程式的執行流程都依賴該計數器來完成 由於 Java 虛擬機器的多執行緒是通過執行緒切換、分配處理器執行時間的方式來執行的,在任一時刻,一個處理器只能執行一條執行緒中的指令。為了保證執行緒切換後能恢復到原來正在執行的位置,每條執行緒都需要有一個獨立的程式計數器。因此程式計數器是執行緒私有的,各個執行緒之間的計數器互不影響,獨立儲存 如果執行緒正在執行的是一個 Java 方法,計數器記錄的就是正在執行的虛擬機器位元組碼指令的地址;如果執行的是本地方法,則計數器對應的值為空。程式計數器是唯一一個在 Java 虛擬機器規範中沒有規定 OutOfMemoryError 情況的區域,至於為什麼,可以看看下面的官方解釋: >
## Java 虛擬機器棧 Java 虛擬機器棧(Java Virtual Machine Stack)也是執行緒私有的,其描述的是 Java 方法執行的執行緒記憶體模型:每個方法被執行時,Java 虛擬機器都會同步建立一個棧幀(Stack Frame)用於儲存區域性變量表、運算元棧、動態連線、方法出口等資訊。每個方法被呼叫直至執行完畢的過程,就對應著一個棧幀從入棧到出棧的過程 區域性變量表存放了編譯器可知的各種 Java 虛擬機器基本資料型別、物件引用以及 returnAddress(指向了一條位元組碼指令的地址)。這些資料型別在區域性變量表中的儲存空間以區域性變數槽(Slot)來表示,其中 64 位的 long 和 double 型別會佔用兩個 Slot,其餘只佔一個。區域性變量表所需的記憶體空間是在編譯期就完成分配的,執行期間不會改變區域性變量表的大小,這裡的大小指的是 Slot 的數量,至於一個 Slot 佔用多少空間,則是由虛擬機器自行決定的事情 在 Java 虛擬機器規範中,對這個記憶體區域規定了兩類異常狀況:如果執行緒請求的棧深度大於虛擬機器所允許的深度,將丟擲 StackOverflowError 異常;如果 Java 虛擬機器棧容量可以動態擴充套件,則當棧擴充套件無法申請足夠的記憶體時會丟擲 OutOfMemoryError 異常(HotSpot 虛擬機器是無法動態擴充套件的,因此只有當執行緒請求棧空間失敗時才會丟擲 OOM 異常)
## Java 堆 Java 堆(Heap)是虛擬機器所管理的記憶體中最大的一塊記憶體區域,被所有執行緒共享,此區域的唯一目的就是存放物件例項,幾乎所有的物件例項都在這裡分配記憶體 Java 堆是垃圾收集器管理的記憶體區域,所以從記憶體回收的角度來看,由於現代垃圾收集器大部分都基於分代收集理論設計,所以 Java 堆中經常出現“新生代”、“老年代”、“永久代”、“Eden 空間”、“From Survivor 空間”、“To Survivor 空間”等名詞。在這裡要指明的是,這些區域僅僅是一部分垃圾收集器的共同特性或者說設計風格而已,並非某個 Java 虛擬機器具體實現的記憶體佈局 根據 Java 虛擬機器規範規定,Java 堆可以處於物理上不連續的記憶體空間,但在邏輯上必須視為連續。但對於大物件(如陣列物件),多數虛擬機器出於實現簡單、儲存高效的考慮,很有可能會要求連續的陣列 Java 堆既可以被實現成固定大小,也可以是可擴充套件的(通過引數 -Xmx 和 -Xms 設定),如果沒有記憶體可分配給例項,並且堆也無法再擴充套件時,會丟擲 OutOfMemoryError 異常
## 執行時常量池 執行時常量池(Runtime Constant Pool)是方法區的一部分。Class 檔案中除了有類的版本、欄位、方法、介面等描述資訊外,還有一項資訊是常量池表(Constant Pool Table),用於存放編譯期生成的各種字面量和符號引用,這部分內容將在類載入後存放到方法區的執行時常量池中 對於執行時常量池,Java 虛擬機器規範並沒有做任何細節的要求,不同的虛擬機器可以根據需要來自主實現。一般來說,除了儲存 Class 檔案中描述的符號引用外,還會把由符號引用翻譯出來的直接引用也儲存在執行時常量池 執行時常量池相對於 Class 檔案常量池的一個重要特徵就是具備動態性,Java 語言並不要求常量一定只能在編譯期才能產生,也就是說,並非預置入 Class 檔案常量池的內容才能進入方法區執行時常量池,執行期間也可以將新的常量放入池中,用得最多的便是 String 類的 intern() 方法
## 直接記憶體 直接記憶體(Direct Memory)並不是虛擬機器執行時資料區的一部分,但這部分也被頻繁使用,也可能導致 OutOfMemoryError 異常 在 JDK1.4 新加入了 NIO 類,這是一種基於通道(Cancel)與緩衝區(Buffer)的 I/O 方式,可以使用 Native 函式直接分配堆外記憶體,然後通過一個儲存在 Java 堆中 DirectByteBuffer 物件作為這塊記憶體的引用進行操作,既能提高效能,還避免了 Java 堆和直接記憶體的頻繁交換資料 本機直接記憶體的分配雖然不受 JVM 限制,但還是會受本機記憶體大小以及處理器定址空間的限制。伺服器管理員配置虛擬機器引數時,同時也要考慮直接記憶體的因素,來設定合理的 -Xmx 等引數信