標籤:register time jvm return 情況 特性 定址 java 邏輯
Java虛擬機器在執行Java程式的過程中會把所管理的記憶體劃分為若干個不同的資料區域,這些地區有各自的用途,有各自的建立時間和銷毀時間,有的地區隨著虛擬機器進程的啟動而存在,有的地區則是依賴使用者線程的啟動和結束進行建立或銷毀。Java虛擬機器第二版規定,虛擬機器管理的記憶體包含以下幾個運行時資料區域
- 程式計數器:程式計數器(Program Counter Register)是一塊較小的記憶體空間,作用可以理解為是當前線程所執行的位元組碼的行號指標。Java虛擬機器的多線程是通過線程切換以及分配處理器執行時間來實現,因此任何時刻,一個處理器只會執行一個線程中的指令,為了線程切換後能恢複到正確的執行位置,每條線程都需要有一個獨立的程式計數器,各線程之間的計數器互不影響,隔離儲存區 (Isolated Storage)。因此程式計數器是線程私人的。程式計數器所佔的記憶體地區是唯一 一個在Java虛擬機器規範中沒有規定任何OutOfMemoryError情況的地區。
- Java虛擬機器棧:Java虛擬機器棧(Java Virtual Machine Stacks)也是線程私人的,它的生命週期與線程相同。虛擬機器棧描述的是Java方法執行的記憶體模型:每個方法被執行的時候都會同時建立一個棧幀用於儲存局部變數表、操作棧、動態連結、方法出口等資訊。每一個方法被調用直至執行完成的過程就對應著一個棧幀在虛擬機器棧中從入棧到出棧的過程。局部變數表存放了編譯期間可知的基礎資料型別 (Elementary Data Type)、對象引用、returnAddress類型(64位長度的long和double類型的資料會佔用2個局部變數空間,其餘的資料類型只佔用1個)。局部變數表所需的記憶體空間在編譯期間已經完成了分配,在方法運行期間不會改變局部變數表的大小。如果線程請求的棧深度大於虛擬機器所允許的深度,將拋出StackOverflowError異常;如果虛擬機器棧可以動態擴充,並且當擴充時無法申請到足夠的記憶體時會拋出OutOfMemoryError異常。
- 本地方法棧:本地方法棧(Native Method Stacks)與虛擬機器棧的作用相似,主要區別是虛擬機器棧為虛擬機器執行Java位元組碼服務,而本地方法棧是為虛擬機器使用到的Native方法服務。本地方法棧地區會拋出StackOverfowError和OutOfMemoryError異常,原因同Java虛擬機器棧。
- Java堆:Java堆(Java Heap)是虛擬機器所管理的記憶體中最大的一塊地區。它是被所有線程共用的一塊記憶體地區,在虛擬機器啟動時建立。Java堆的唯一目的就是存放對象執行個體(Java虛擬機器規範描述是:所有的對象執行個體以及數組都要在堆上分配)。但隨著JIT編譯器的發展與逃逸分析技術的逐漸成熟,棧上分配、標量替換最佳化技術會導致一些微妙的變化,所有的對象都分配在堆上已經不是那麼絕對了。Java堆是垃圾收集器管理的主要區域,被稱為“GC堆”,堆中劃分了很細緻的記憶體空間地區,但無論如何劃分,都與存放內容無關,無論哪個記憶體空間地區,儲存的都仍然是對象執行個體,進一步劃分的目的是為了更好的回收記憶體或者更快的分配記憶體。Java堆可以處於物理上不連續的記憶體空間,只要邏輯上是連續的就可以。堆通過-Xmx和-Xms控制最大和最小的堆空間分配,如果在堆中已經沒有足夠的記憶體來完成執行個體分配,並且堆也無法再擴充的時候,將會拋出OutOfMemoryError異常。
- 方法區:方法區(Method Area)與堆一樣,是各個線程共用的記憶體地區,方法區用於儲存已被虛擬機器載入的類資訊、常量、靜態變數、即時編譯器編譯後的代碼等資料。Java虛擬機器規範中把方法區描述為堆的一個邏輯部分,但其實方法區有一個別名叫“Non-Heap”。方法區不需要連續的記憶體、可以選擇固定大小或者擴充、可以選擇不實現垃圾收集。方法區的記憶體回收目標主要是針對常量池的回收和對類型的卸載。Java虛擬機器規範規定當方法區無法滿足記憶體配置需求時,將拋出OutOfMemoryError異常。
- 運行時常量池:運行時常量池(Runtime Constant Pool)是方法區的一部分。Class檔案中除了有類的版本、欄位、方法、介面等描述資訊外,還有一項資訊是常量池(Constant PoolTable),常量池用於存放編譯期產生的各種字面量和符號引用,這部分內容在類載入後存放到方法區的運行時常量池中。Java虛擬機器對Class檔案的每個部分的格式都有嚴格的規定,但對運行時常量池,Java虛擬機器規範沒有做任何要求。運行時常量池相比Class檔案常量池有一個重要特性,運行時常量池具有動態性。意思是說並非預置入Class檔案中常量池的內容才能進入方法區運行時常量池,運行期間也可能將新的常量放入池中,比如String.intern();。運行時常量池會受到方法區記憶體的限制,當常量池無法再申請到記憶體時會拋出OutOfMemoryError異常。
- 直接記憶體:直接記憶體(Direct Memory)並不是虛擬機器運行時資料區的一部分,也不是Java虛擬機器規範中定義的記憶體地區,但這部分記憶體可能導致OutOfMemoryError異常出現。比如nio(new i/o)使用native函數庫直接分配堆外記憶體,顯然電腦直接記憶體的分配不會受到Java堆大小的限制,但是既然是記憶體,總會受到限制(本機總記憶體的大小、處理器定址空間的限制)。有時JVM最佳化人員配參數時,經常忽略掉直接記憶體,使得各個記憶體地區的總和大於實體記憶體限制,從而導致動態擴充時出現OutOfMemoryError異常。
Java記憶體地區 - 深入Java虛擬機器讀後總結