1. 程式人生 > >java虛擬機器執行時資料區

java虛擬機器執行時資料區

Java在執行Java程式的過程中會把它所管理的記憶體劃分為若干個不同的資料區域。這些區域都有各自的用途、建立和銷燬的時間,有一些是隨虛擬機器的啟動而建立,隨虛擬機器的退出而銷燬,有些則是與執行緒一一對應,隨執行緒的開始和結束而建立和銷燬。

Java虛擬機器所管理的記憶體將會包括以下幾個執行時資料區域

執行時資料區

程式計數器(Program Counter Register)

它是一塊較小的記憶體空間,它的作用可以看做是當先執行緒所執行的位元組碼的訊號指示器。

每一條JVM執行緒都有自己的PC暫存器,各條執行緒之間互不影響,獨立儲存,這類記憶體區域被稱為“執行緒私有”記憶體

在任意時刻,一條JVM執行緒只會執行一個方法的程式碼。該方法稱為該執行緒的當前方法(Current Method)

如果該方法是java方法,那PC暫存器儲存JVM正在執行的位元組碼指令的地址

如果該方法是native,那PC暫存器的值是undefined。

此記憶體區域是唯一一個在Java虛擬機器規範中沒有規定任何OutOfMemoryError情況的區域。

Java虛擬機器棧(Java Virtual Machine Stack)

與PC暫存器一樣,Java虛擬機器棧也是執行緒私有的。每一個JVM執行緒都有自己的java虛擬機器棧,這個棧與執行緒同時建立,它的生命週期與執行緒相同。

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

JVM stack 可以被實現成固定大小,也可以根據計算動態擴充套件。

如果採用固定大小的JVM stack設計,那麼每一條執行緒的JVM Stack容量應該線上程建立時獨立地選定。JVM實現應該提供調節JVM Stack初始容量的手段;如果採用動態擴充套件和收縮的JVM Stack方式,應該提供調節最大、最小容量的手段。

如果執行緒請求的棧深度大於虛擬機器所允許的深度將丟擲StackOverflowError;

如果JVM Stack可以動態擴充套件,但是在嘗試擴充套件時無法申請到足夠的記憶體時丟擲OutOfMemoryError。

本地方法棧(Native Method Stack)

本地方法棧與虛擬機器棧作用相似,後者為虛擬機器執行Java方法服務,而前者為虛擬機器用到的Native方法服務。

虛擬機器規範對於本地方法棧中方法使用的語言,使用方式和資料結構沒有強制規定,甚至有的虛擬機器(比如HotSpot)直接把二者合二為一。

這玩意兒丟擲的異常跟上面的虛擬機器棧一樣。

Java堆(Java Heap)

虛擬機器管理的記憶體中最大的一塊,同時也是被所有執行緒所共享的,它在虛擬機器啟動時建立,這貨存在的意義就是存放物件例項,幾乎所有的物件例項以及陣列都要在這裡分配記憶體。這裡面的物件被自動管理,也就是俗稱的GC(Garbage Collector)所管理。用就是了,有GC扛著呢,不用操心銷燬回收的事兒。

Java堆的容量可以是固定大小,也可以隨著需求動態擴充套件(-Xms和-Xmx),並在不需要過多空間時自動收縮。

Java堆所使用的記憶體不需要保證是物理連續的,只要邏輯上是連續的即可。

JVM實現應當提供給程式設計師調節Java 堆初始容量的手段,對於可動態擴充套件和收縮的堆來說,則應當提供調節其最大和最小容量的手段。

如果堆中沒有記憶體完成例項分配並且堆也無法擴充套件,就會拋OutOfMemoryError。

方法區(Method Area

跟堆一樣是被各個執行緒共享的記憶體區域,用於儲存以被虛擬機器載入的類資訊、常量、靜態變數、即時編譯器編譯後的程式碼等資料。雖然這個區域被虛擬機器規範把方法區描述為堆的一個邏輯部分,但是它的別名叫非堆,用來與堆做一下區別。

方法區在虛擬機器啟動的時候建立。

方法區的容量可以是固定大小的,也可以隨著程式執行的需求動態擴充套件,並在不需要過多空間時自動收縮。

方法區在實際記憶體空間中可以是不連續的。

Java虛擬機器實現應當提供給程式設計師或者終端使用者調節方法區初始容量的手段,對於可以動態擴充套件和收縮方法區來說,則應當提供調節其最大、最小容量的手段。

當方法區無法滿足記憶體分配需求時就會拋OutOfMemoryError。

這裡有一個小例子,來說明堆,棧和方法區之間的關係的

public class Test2 {
    public static void main(String[] args) {
        public Test2 t2 = new Test2();
        //JVM將Test2類資訊載入到方法區,new Test2()例項儲存在堆區,Test2引用儲存在棧區  
    }
}  

執行時常量池(Runtime Constant Pool

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

Java虛擬機器對Class檔案的每一部分(自然也包括常量池)的格式都有嚴格的規定,每一個位元組用於儲存哪種資料都必須符合規範上的要求,這樣才會被虛擬機器認可、裝載和執行。但對於執行時常量池,Java虛擬機器規範沒有做任何細節的要求,不同的提供商實現的虛擬機器可以按照自己的需要來實現這個記憶體區域。不過,一般來說,除了儲存Class檔案中描述的符號引用外,還會把翻譯出來的直接引用也儲存在執行時常量池中。

執行時常量池相對於Class檔案常量池的另外一個重要特徵是具備動態性,Java語言並不要求常量一定只能在編譯期產生,也就是並非預置入Class檔案中常量池的內容才能進入方法區執行時常量池,執行期間也可能將新的常量放入池中,這種特性被開發人員利用得比較多的便是String類的intern()方法。

既然執行時常量池是方法區的一部分,自然會受到方法區記憶體的限制,當常量池無法再申請到記憶體時會丟擲OutOfMemoryError異常。

直接記憶體(Direct Memory)

直接記憶體(Direct Memory)並不是虛擬機器執行時資料區的一部分,也不是Java虛擬機器規範中定義的記憶體區域,但是這部分記憶體也被頻繁地使用,而且也可能導致OutOfMemoryError異常出現。

JDK1.4加的NIO中,ByteBuffer有個方法是allocateDirect(int capacity) ,這是一種基於通道(Channel)與緩衝區(Buffer)的I/O方式,它可以使用Native函式庫直接分配堆外記憶體,然後通過一個儲存在Java堆裡面的DirectByteBuffer物件作為這塊記憶體的引用進行操作。這樣能在一些場景中顯著提高效能,因為避免了在Java堆和Native堆中來回複製資料。

顯然,本機直接記憶體的分配不會受到Java堆大小的限制,但是,既然是記憶體,則肯定還是會受到本機總記憶體(包括RAM及SWAP區或者分頁檔案)的大小及處理器定址空間的限制。伺服器管理員配置虛擬機器引數時,一般會根據實際記憶體設定-Xmx等引數資訊,但經常會忽略掉直接記憶體,使得各個記憶體區域的總和大於實體記憶體限制(包括物理上的和作業系統級的限制),從而導致動態擴充套件時出現OutOfMemoryError異常。