《深入理解java虛擬機器》學習筆記1——Java記憶體結構

yangxi_001發表於2013-12-04

java虛擬機器規範規定的java虛擬機器記憶體其實就是java虛擬機器執行時資料區,其架構如下:

其中方法區和堆是由所有執行緒共享的資料區。

Java虛擬機器棧,本地方法棧和程式計數器是執行緒隔離的資料區。

(1).程式計數器:

是一塊較小的記憶體空間,其作用可以看作是當前執行緒所執行的位元組碼的行號指示器,位元組碼解析器工作時通過改變程式計數器的值來選取下一條需要執行的位元組碼指令。程式的分支、迴圈、跳轉、異常處理以及執行緒恢復等基礎功能都是依賴程式計數器來完成。

Java虛擬機器的多執行緒是通過執行緒輪流切換並分配處理器執行時間片來實現,在任何一個時刻,一個處理器只會執行一條執行緒指令,因此,為了確保執行緒切換之後能恢復到正確的執行位置,每條執行緒都需要一個獨立的程式計數器,因此程式計數器是執行緒私有的記憶體。

程式計數器是java虛擬機器中唯一一個沒有規定任何記憶體溢位OutOfMemoryError的記憶體區域。

(2).java虛擬機器棧:

Java虛擬機器棧也是執行緒私有的,它的生命週期與執行緒相同。虛擬機器棧描述的是java方法執行的記憶體模型:每個方法被執行時都會同時建立一個棧幀用於存放區域性變數表、運算元棧、動態連線和方法出口等資訊。每個方法被呼叫直至執行完成過程,就對應著一個棧幀在虛擬機器中從入棧到出棧的過程。

Java虛擬機器棧的區域性變數表存放了編譯器可知的8種java基本型別資料、物件引用(注意不是物件例項本身)、方法返回地址returnAddress。

Java虛擬機器棧的區域性變數表空間單位是槽(Slot),其中64位長度的double和long型別會佔用兩個slot,其餘的資料型別只佔用一個slot。區域性變數表所需記憶體空間在編譯期間完成分配,當進入一個方法時,該方法需要在幀中分配多大的區域性變數空間是完全確定的,在方法執行期間不會改變區域性變數表的大小。

Java虛擬機器棧有兩種異常狀況:如果執行緒請求的棧深度大於虛擬機器所允許的最大深度時,丟擲StackOverflowError異常;如果虛擬機器棧可以動態擴充套件,當擴充套件時無法申請到足夠記憶體時會丟擲OutOfMemoryError異常。

(3).本地方法棧:

本地方法棧與java虛擬機器棧作用非常類似,其區別是:java虛擬機器棧是為虛擬機器執行java方法服務,而本地方法棧是為虛擬機器呼叫的作業系統本地方法服務。

Java虛擬機器規範沒有對本地方法棧的實現和資料結構做強制規定,Sun HotSpot虛擬機器直接把java虛擬機器棧和本地方法棧合二為一。

與java虛擬機器棧類似,本地方法棧也會丟擲StackOverflowError異常和OutOfMemoryError異常。

(4).堆:

堆是java虛擬機器所管理的記憶體區域中最大一塊,java堆是被所有執行緒所共享的一塊記憶體區域,在java虛擬機器啟動時建立,堆記憶體的唯一目的就是存放物件例項。幾乎所有的物件例項都是在堆分配記憶體。

Java堆是垃圾收集器管理的主要區域,從垃圾回收的角度看,由於現在的垃圾收集器基本都採用的是分代收集演算法,因此java堆還可以初步細分為新生代和年老代。

Java虛擬機器規範規定,堆可以處於物理上不連續的記憶體空間中,只要邏輯上是連續的即可。在實現上即可以是固定大小的,也可以是可動態擴充套件的。如果在堆中沒有記憶體完成例項分配,並且堆大小也無法在擴充套件時,將會丟擲OutOfMemoryError異常。

(5).方法區:

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

Sun HotSpot虛擬機器把方法區叫永久代(Permanent Generation),方法區中最重要的部分是執行時常量池。Class檔案中除了有類的版本、欄位、方法、介面等描述資訊外,還有一項資訊是常量池,用於存放編譯期生成的各種字面變數、符號引用、直接引用等,這些內容將在類載入後存放到方法區的執行時常量池中,另外在執行期間也可以將新的常量存放到常量池中,如String的intern()方法。

方法區和執行時常量池在無法滿足記憶體分配時,也會丟擲OutOfMemoryError異常。

(6).直接記憶體:

直接記憶體並不是java虛擬機器執行時資料區的一部分,也不是java虛擬機器規範中定義的記憶體區域,但是在java開發中還是會使用到。

JDK1.4中新引入的NIO(new I/O),引入了一種基於通道(Channel)和緩衝區(Buffer)的I/O方式,可以使用作業系統本地方法庫直接分配堆外記憶體,然後通過一個儲存在java堆裡面的DirectByteBuffer物件作為堆外直接記憶體的引用進行操作,避免了java堆記憶體和本地直接記憶體間的資料拷貝,可以顯著提高效能。

雖然直接記憶體並不直接收到java虛擬機器記憶體影響,但是如果java虛擬機器各個記憶體區域總和大於實體記憶體限制,從而導致直接記憶體不足,動態擴充套件時也會丟擲OutOfMemoryError異常。

java虛擬機器記憶體結構中的程式計數器、虛擬機器棧和本地方法棧這三個區域隨執行緒建立而生,隨執行緒銷燬而滅,因此這三個區域的記憶體分配和回收是確定的,java垃圾收集器重點關注的是java虛擬機器的堆記憶體和方法區記憶體。

相關文章