Java虛擬機 運行時數據區

【轉】http://www.cnblogs.com/Cratical/archive/2012/08/21/2649985.html

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異常。

發佈了26 篇原創文章 · 獲贊 9 · 訪問量 24萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章