標籤:
簡介
瞭解Java虛擬機器記憶體分布的好處
1.瞭解Java記憶體管理的細節,有助於程式員編寫出效能更好的程式。比如,在新的線程建立時,JVM會為每個線程建立一個專屬的棧 (stack),其棧是先進後出的資料結構,這種方式的特點,讓程式員編程時,必須特別注意遞迴方法要盡量少使用,另外棧的大小也有一定的限制,如果過多 的遞迴,容易導致stack overflow。
2.瞭解Java記憶體管理的細節,一旦記憶體管理出現問題,有助於找到問題的根本原因所在。
3.瞭解Java記憶體管理的內幕,有助於最佳化JVM,從而使得自己的應用獲得最佳的效能體驗。
JVM的體繫結構如下
如所示,JVM的體繫結構包含幾個主要的子系統和記憶體區:
類裝載子系統
負責把類從檔案系統中裝入記憶體
GC子系統
垃圾收集器的主要工作室自動回收不再啟動並執行程式引用對象所佔用的記憶體,此外,它還可能負責那些還在使用的對象,以減少的堆片段。
記憶體區
用於儲存位元組碼,程式運行時建立的對象,傳遞給方法的參數,傳回值,局部變數和中間計算結果。
執行引擎
1、最簡單的:一次性解釋位元組碼。
2、快,但消耗記憶體的:“即時編譯器”,第一次被執行的位元組碼會被編譯成機器代碼,放入緩衝,以後調用可以重用。
3、自適應最佳化器,虛擬機器開始的時候會解釋位元組碼,但是會監視運行中程式的活動,並記錄下使用最頻繁的程式碼片段。程式啟動並執行時候,虛擬機器只把使用最頻繁的代碼編譯成本地代碼,其他的代碼由於使用的並不頻繁,繼續保留為位元組碼--由虛擬機器繼續解釋他們。一般可以使java虛擬機器80%~90%的時間裡執行被最佳化過的本地代碼,只需要編譯10%~20%對效能優影響的代碼。
4、由硬體晶片集成,他用本地方法執行java位元組碼,這種執行引擎實際上是內嵌在晶片裡的。
程式計數器(Program Counter Register)
它是一塊較小的記憶體空間,它的作用可以看做是當先線程所執行的位元組碼的訊號指標。
每一條JVM線程都有自己的PC寄存器,各條線程之間互不影響,隔離儲存區 (Isolated Storage),這類記憶體地區被稱為“線程私人”記憶體。
在任意時刻,一條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。
這裡有一個小例子,來說明堆,棧和方法區之間的關係的
1 public class Test2 {2 public static void main(String[] args) {3 public Test2 t2 = new Test2();4 //JVM將Test2類資訊載入到方法區,new Test2()執行個體儲存在堆區,Test2引用儲存在棧區 5 }6 }
運行時常量池(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異常。
鳴謝
小弟學習Java虛擬機器時並沒有閱讀書籍,主要是查看部落格和相關文檔,特地在此謝謝以下前輩們的精彩部落格。
1.http://www.cnblogs.com/Cratical/archive/2012/08/21/2649985.html
2.http://hllvm.group.iteye.com/group/wiki/3053-JVM
Java虛擬機器記憶體配置詳解