1. 程式人生 > >JVM-執行時資料區域

JVM-執行時資料區域

 

1、程式計數器

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

由於Java虛擬機器的多執行緒是通過執行緒輪流切換並分配處理器執行時間的方式來實現的,為了執行緒切換後能恢復到正確執行位置,每個執行緒都需要一個獨立的程式計數器來獨立儲存,稱這類記憶體區域為“執行緒私有”的記憶體。

如果執行緒正在執行一個Java方法,那麼計數器記錄的是正在執行的虛擬機器位元組碼指令的地址;如果執行的是一個Native方法(本地方法),則計數器值為空。此記憶體區域是唯一一個在Java虛擬機器規範中沒有規定任何OutOfMemoryError情況的區域。

 

2、Java虛擬機器棧

Java虛擬機器棧(Java Virtual Machine Stacks)也是執行緒私有的,生命週期與執行緒相同。虛擬機器棧描述的是Java方法執行的記憶體模型:每個方法在執行的同時都會建立一個棧幀(Stack Frame)用於儲存區域性變量表、運算元棧、動態連結、方法出口等資訊。每個方法從呼叫到執行完成的過程,就對應一個棧幀在虛擬機器棧中入棧到出棧的過程。

  • 區域性變量表:
    • 存放編譯期可知的各種基本資料型別(boolean、byte、char、short、int、float、long、double)、物件引用(reference型別,不等同於物件本身,可能是一個指向物件起始地址的引用指標,也可能是指向一個代表物件的控制代碼或其他與此物件相關的位置)和returnAddress型別(指向一條位元組碼指令的地址:函式返回地址)。
    • long、double佔用兩個區域性變數控制元件Slot,其餘的資料型別只佔用1個。
    • 區域性變量表所需的記憶體空間在編譯期確定,當進入一個方法時,方法在棧幀中所需要分配的區域性變數控制元件是完全確定的,不可動態改變大小。
    • 異常:執行緒請求的棧幀深度大於虛擬機器所允許的深度---StackOverFlowError,如果虛擬機器棧可以動態擴充套件(大部分虛擬機器允許動態擴充套件,也可以設定固定大小的虛擬機器棧),但是無法申請到足夠的記憶體---OutOfMemorError。
  • 運算元棧:

    • 後進先出LIFO,最大深度由編譯期確定。棧幀剛建立使,運算元棧為空,執行方法操作時,運算元棧用於存放JVM從區域性變量表複製的常量或者變數,提供提取,及結果入棧,也用於存放呼叫方法需要的引數及接受方法返回的結果。
    • 運算元棧可以存放一個jvm中定義的任意資料型別的值。
    • 在任意時刻,運算元棧都一個固定的棧深度,基本型別除了long、double佔用兩個深度,其它佔用一個深度
  • 動態連結:
    •  每個棧幀都包含一個指向執行時常量池中該棧幀所屬方法的引用,持有這個引用是為了支援方法呼叫過程中的動態連線。Class檔案的常量池中存在有大量的符號引用,位元組碼中的方法呼叫指令就以常量池中指向方法的符號引用為引數。這些符號引用,一部分會在類載入階段或第一次使用的時候轉化為直接引用(如final、static域等),稱為靜態解析,另一部分將在每一次的執行期間轉化為直接引用,這部分稱為動態連線。

  • 方法返回地址:
    • 當一個方法被執行後,有兩種方式退出該方法:執行引擎遇到了任意一個方法返回的位元組碼指令或遇到了異常,並且該異常沒有在方法體內得到處理。無論採用何種退出方式,在方法退出之後,都需要返回到方法被呼叫的位置,程式才能繼續執行。方法返回時可能需要在棧幀中儲存一些資訊,用來幫助恢復它的上層方法的執行狀態。一般來說,方法正常退出時,呼叫者的PC計數器的值就可以作為返回地址,棧幀中很可能儲存了這個計數器值,而方法異常退出時,返回地址是要通過異常處理器來確定的,棧幀中一般不會儲存這部分資訊。
    • 方法退出的過程實際上等同於把當前棧幀出棧,因此退出時可能執行的操作有:恢復上層方法的區域性變量表和運算元棧,如果有返回值,則把它壓入呼叫者棧幀的運算元棧中,調整PC計數器的值以指向方法呼叫指令後面的一條指令。

3、本地方法棧

本地方法棧(Native Method Stack)與虛擬機器棧作用相似,只不過它為虛擬機器使用Native方法服務。其中本地方法棧中方法使用的語言、使用方法與資料結構沒有強制規定,因此具體的虛擬機器可以自由實現它。

4、Java堆

Java堆是Java虛擬機器管理的記憶體中最大的一塊,所有執行緒共享,在虛擬機器啟動時建立的。唯一目的就是存放物件例項,幾乎所有物件例項都在這裡分配記憶體(但隨著JIT編譯器發展與逃逸分析技術逐漸成熟,就不是那麼絕對了)。

Java堆是垃圾收集器管理的主要區域,很多時候也被稱為“GC堆”(Garbage Collected Heap)。從記憶體回收角度看,由於現在收集器基本採用分代收集演算法,所有Java堆可細分為:新生代和老年代,再細緻點有Eden空間、From Survivor空間、To Survivor空間等。從記憶體分配角度看,執行緒共享的Java堆可以劃分出多個執行緒私有的分配緩衝器(Thread Local Allocation Buffer, TLAB)。

  根據的Java虛擬機器規範,Java的堆可以處於物理不連續的空間中,只要邏輯連續即可。在實現時,既可以實現成固定大小的也可以是可擴充套件的(通過-Xmx和-Xms控制),如果堆中沒有足夠的記憶體完成例項分配,並且堆也無法得到擴充套件時,將會丟擲的OutOfMemoryError異常。

5、方法區

方法區(Method Area)與Java堆一樣,也是各個執行緒共享的記憶體區域,它用於儲存已被虛擬機器載入的類資訊、常量、靜態變數、即時編譯器編譯後的程式碼等資料。

Java虛擬機器規範把方法區描述為堆的一個邏輯部分,其實堆和方法區可以看成資料部分;虛擬機器棧和程式計數器可以看成指令部分;方法區儲存一些不會變更的資料,之前熱點上使用GC分代收集管理方法區,所以方法區也被稱為永久代(本質上兩者不等價),但是現在已經使用Native Memory來代替永久代了。

虛擬機器對方法區規範非常寬鬆,除了和Java的堆一樣不需要連續的記憶體和可以選擇固定大小意外,還可以選擇不實現垃圾回收。垃圾回收行為在這個區域比較少見但還是有必要的,主要是針對常量池回收和型別的解除安裝。
 

6、執行時常量池

執行時常量池是方法區的一部分,用於存放編譯期生成的各種字面量和符號引用,執行時常量池相對於類常量池另外一個特性就是具備動態性,執行期間可能將新的常量放入池中。 當常量池無法再申請到記憶體時會丟擲OutOfMemoryError異常。