1. 程式人生 > >Java虛擬機器——執行時資料區域

Java虛擬機器——執行時資料區域

java虛擬機器章執行java程式的過程中會把它所管理的記憶體劃分為若干個不同的資料區域,這些區域都有各自的用途。

1、程式計數器(執行緒私有

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

因為處理器在一個確定是時刻只會執行一個執行緒中的指令,執行緒切換後,是通過計數器來記錄執行痕跡的,因而可以看出,程式計數器是每個執行緒私有的。

  如果執行的是java方法,那麼記錄的是正在執行的虛擬機器位元組碼指令的地址的地址,如果是native方法,計數器的值為

空(undefined)。

而且這個區域是java虛擬機器規範中唯一一個沒有規定任何OutOfMemoryError情況的區域

2、java虛擬機器棧(執行緒私有,生命週期與執行緒相同

虛擬機器棧描述的是java方法執行的記憶體模型:每個方法在執行的同時會建立一個棧幀用於儲存區域性變量表、運算元棧、動態連結、方法出口燈資訊。每一個方法從呼叫直至執行完成的過程,就對應著一個棧幀在虛擬機器中入棧到出棧的過程。

  • 區域性變量表:
    • 存放編譯期可知的各種基本資料型別、物件引用型別和returnAddress型別(指向一條位元組碼指令的地址:函式返回地址)。
    • 區域性變量表所需的記憶體空間在編譯期確定,當進入一個方法時,方法在棧幀中所需要分配的區域性變數控制元件是完全確定的,不可動態改變大小。
    • 異常:執行緒請求的棧幀深度大於虛擬機器所允許的深度---StackOverFlowError,如果虛擬機器棧可以動態擴充套件(大部分虛擬機器允許動態擴充套件,也可以設定固定大小的虛擬機器棧),但是無法申請到足夠的記憶體---OutOfMemorError。
  • 運算元棧:

    • 後進先出LIFO,最大深度由編譯期確定。棧幀剛建立使,運算元棧為空,執行方法操作時,運算元棧用於存放JVM從區域性變量表複製的常量或者變數,提供提取,及結果入棧,也用於存放呼叫方法需要的引數及接受方法返回的結果。
    • 運算元棧可以存放一個jvm中定義的任意資料型別的值。
    • 在任意時刻,運算元棧都一個固定的棧深度,基本型別除了long、double佔用兩個深度,其它佔用一個深度
  • 動態連線:

          每個棧幀都包含一個指向執行時常量池中該棧幀所屬方法的引用,持有這個引用是為了支援方法呼叫過程中的動態連線。Class檔案的常量池中存在有大量的符號引用,位元組碼中的方法呼叫指令就以常量池中指向方法的符號引用為引數。這些符號引用,一部分會在類載入階段或第一次使用的時候轉化為直接引用(如final、static域等),稱為靜態解析,另一部分將在每一次的執行期間轉化為直接引用,這部分稱為動態連線。

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

3、本地方法棧

本地方法棧和虛擬機器棧的作用非常類似,區別是虛擬機器棧執行java方法,本地方法棧執本地方法(Native宣告的非java語言的方法)。有的虛擬機器(例如Sun HotSpot)將本地方法棧和虛擬機器方法棧合二為一。本地方法棧也會丟擲StackOverflowError和OutOfMemoryError異常。

4、java堆(所有執行緒共享)

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

java堆也是垃圾收集器管理的主要區域,因此很多時候也被成為 “GC堆” 。根據java虛擬機器規範,堆可以是連續的實體記憶體,也可以是不連續的實體記憶體,只要邏輯上連續即可。堆在實現時,既可以是固定大小的,也可以是可擴充套件的。如果堆中沒有足夠記憶體來完成例項分配,並且堆也無法再擴充套件時,將會丟擲OutOfMemoryError異常。

5、方法區(所有執行緒共享)

方法區主要用於儲存已經被虛擬機器載入的類資訊、常量、靜態變數等資料。在HotSpot虛擬機器上又被叫做 “ 永久代 ”,是以為HotSpot虛擬機器設計團隊把GC分帶收集擴充套件至方法區,或者說是用永久代實現了方法區,這樣垃圾收集器可以想管理java堆一樣來管理這部分記憶體。

同樣,當方法區也無法再擴充套件時,將會丟擲OutOfMemoryError異常。

6、執行時常量池

執行時常量池,也是方法區的一部分。Class檔案中除了有類的版本、欄位、方法、介面等資訊外,還有一個區域是常量池,用於存放編譯期間生成的各種字面量和符號的引用,在類載入後,在方法區的常量池存放。

常量池是方法區的一部分,所以同樣,當無法再擴充套件時,將會丟擲OutOfMemoryError異常。

7、直接記憶體、

直接記憶體並不是虛擬機器執行時資料區的一部分,也不是java虛擬機器規範中的記憶體區域。

在jdk 1.4中引入了NIO(New I/O)一種基於通道和緩衝區(Buffer)的IO方式,它可以使用Native函式庫直接分配堆外記憶體,然後通過一些儲存在java堆中的物件作為這塊記憶體區域的引用來進行操作。

直接記憶體會受到本機記憶體的限制,會丟擲OutOfMemoryError異常。