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

huaye666發表於2019-05-09
  • 執行時資料區域

    • 根據《《Java虛擬機器規範》》的規定,Java虛擬機器所管理的記憶體包括以下幾個執行時資料區域。
      java虛擬機器——執行時資料區域
      其中方法區和堆都是執行緒“共享”的,而虛擬機器棧,本地方法棧和程式計數器則是每個執行緒“私有“的資料區。

值得注意的是,上圖執行時資料區域的劃分只是概念模型,就像OSI七層模型一樣,實際實現時並不是嚴格按圖所示劃分,而且不同的虛擬機器實現方式也不同,這裡主要討論HotSpot VM。

  • 程式計數器

    • 程式計數器是一塊較小的記憶體空間,我們都知道java程式碼都是以位元組碼指令的形式在jvm上執行(先不考慮JIT),而程式計數器就可以看做當前執行緒執行的位元組碼指令的行號指示器。 通過改變計數器的值就可以改變下一條要執行的指令,也就是說程式碼的分支,迴圈,跳轉,異常處理,執行緒恢復等功能都需要依賴計數器來完成。
    • 程式計數器是唯一一個沒有規定任何OutOfMemoryError的資料區。

當執行的是java方法,計數器記錄的是位元組碼指令的地址,而執行Native方法時,計數器的值為空(Undefined)。

  • Java虛擬機器棧

    • 虛擬機器棧是描述java方法執行的記憶體模型,它的本質是一個由棧幀組成的棧,每個棧幀都用於儲存著區域性變數表,運算元棧,動態連結,方法出口等資訊。 每一個方法的執行都會建立一個棧幀,兩者是相對應的,而每一個方法的呼叫到結束的過程也對應了一個棧幀的入棧和出棧操作。
    • 我們常常把Java記憶體粗略的區分為”堆記憶體“和”棧記憶體“,其中的”棧記憶體“其實就是指Java虛擬機器棧,或者準確來說是虛擬機器棧中的區域性變數表。
    • 區域性變數表存放了編譯可知的各種基本資料型別(boolean,byte,char,short,int,float,long,double)、物件應用(reference)和returnAddress型別(指向了一條位元組碼指令的地址)。區域性變數所需的記憶體空間在編譯期間完成分配,所以一個方法需要在幀中分配多大的區域性變數空間是確定的,且在執行期間不會改變大小。
    • Java虛擬機器棧會丟擲兩種異常,分別是執行緒請求的棧深度大於虛擬機器允許的深度,將丟擲的StackOverflowError異常,和虛擬機器棧動態擴充套件時無法申請到足夠的記憶體就會丟擲的OutOfMemoryError異常。

    虛擬機器訪問物件的方式有兩種——控制程式碼和直接指標,當使用控制程式碼訪問時reference儲存的就是物件的控制程式碼地址,相反則儲存的是物件的直接地址。HotSpot VM用的是後者。

  • 本地方法棧

    • 本地方法棧的作用跟虛擬機器棧的作用非常相似,只不過虛擬機器棧對應的是java方法,而本地方法棧對應的是Native方法。由於虛擬機器規範沒有對本地方法棧中方法使用的語言、使用方式和資料結構進行規範,所以不同的虛擬機器有不用的實現方法,而HotSpot VM直接把本地方法棧和虛擬機器棧合二為一。
  • Java堆

    • Java堆是所有執行緒共享的一塊記憶體區域,對很多應用來說是虛擬機器管理的最大的一塊區域,也是垃圾收集器管理的主要區域。
    • 根據虛擬機器規範Java堆的唯一目的就是存放物件,幾乎所有的物件例項都在這裡分配記憶體。該區域的記憶體分配可以從兩個角度來看:從記憶體回收的角度看,由於現在收集器大多采用分代收集的演算法,所以Java堆可以細分為:新生代和老年代。從記憶體分配的角度來看,執行緒共享的Java堆中可能劃分出多個執行緒私有的分配緩衝區。( 分配緩衝區是為了解決多執行緒建立物件分配記憶體時的執行緒安全問題,而為每個執行緒劃分的記憶體空間,不同執行緒在各自的緩衝空間中分配記憶體,只有緩衝區用完並分配新的緩衝區時,才同步鎖定)
    • Java堆可以通過-Xmx和-Xms來固定大小,當堆中記憶體不足且無法擴充套件時,就會丟擲OutOfMemoryError異常。

    Java堆可以處於物理上不連續的記憶體空間中,只要邏輯上聯絡即可

  • 方法區

    • 方法區與java堆一樣是執行緒共享的記憶體區域,它用於儲存被虛擬機器載入的類的類資訊,常量,靜態變數,即時編譯器編譯後的程式碼等資料。在HotSpot VM中用”永久代“來實現方法區,但是由於”永久代“有記憶體大小上限(-XXMaxPermSize)所以在一些動態建立物件的應用中(如jsp)容易出現記憶體溢位的問題,在JDK1.8後”永久代“被”元空間“取代。
    • 執行時常量池是方法區的一部分。我們平常所說的常量池就是指執行時常量池,實際上常量池有兩種形態,一種是指.class檔案中的常量池——.class檔案中除了有類的版本,欄位,方法,介面等描述資訊外,還有一項就是常量池,用於存放字面量和符號引用。 其中的字面量也就是java語言層面上的常量,如字串,和final修飾的常量值。而符號引用則包括三種:類和介面的全限命名、欄位名稱和修飾符號、方法名稱和修飾符號。 而.class檔案的常量池在虛擬機器加類載入完後都放在執行時常量池中存放。
    • 執行時常量池中的常量不一定在編譯期間產生,意思是並非只有class檔案中的常量可以置入常量池中,在執行期間也可以將新常量放入池中,用得最多的是String的intern()方法。

    也因此在jdk1.7中字串常量池從”永久代“中移出。

相關文章