天天看點

java虛拟機中的記憶體配置設定

文章目錄

      • 1.程式計數器
      • 2.虛拟機棧
      • 3.本地方法棧
      • 4.堆區
      • 5.方法區

java虛拟機中記憶體劃分如下圖所示,下面具體談一下每一個部分。

java虛拟機中的記憶體配置設定

1.程式計數器

程式計數器是一個比較小的記憶體區域,用于訓示目前線程所執行的位元組碼執行到了第幾行,類似于彙編語言的程式計數器。每個程式計數器隻用來記錄一個線程的行号,是以它是線程私有(一個線程就有一個程式計數器)的。

如果程式執行的是一個Java方法,則計數器記錄的是正在執行的虛拟機位元組碼指令位址;如果正在執行的是一個本地(native,由C語言編寫完成)方法,則計數器的值為Undefined,由于程式計數器隻是記錄目前指令位址,是以不存在記憶體溢出的情況,是以,程式計數器也是所有JVM記憶體區域中唯一一個沒有定義OutOfMemoryError的區域。

2.虛拟機棧

一個線程的每個方法在執行的同時,都會建立一個棧幀(Statck Frame),棧幀中存儲的有局部變量表、操作站、動态連結、方法出口等,當方法被調用時,棧幀在JVM棧中入棧,當方法執行完成時,棧幀出棧。

局部變量表中存儲着方法的相關局部變量,包括各種基本資料類型,對象的引用,傳回位址等。在局部變量表中,隻有long和double類型會占用2個局部變量空間(Slot,對于32位機器,一個Slot就是32個bit),其它都是1個Slot。需要注意的是,局部變量表是在編譯時就已經确定好的,方法運作所需要配置設定的空間在棧幀中是完全确定的,在方法的生命周期内都不會改變。

虛拟機棧中定義了兩種異常,如果線程調用的棧深度大于虛拟機允許的最大深度,則抛出StatckOverFlowError(棧溢出);不過多數Java虛拟機都允許動态擴充虛拟機棧的大小(有少部分是固定長度的),是以線程可以一直申請棧,直到記憶體不足,此時,會抛出OutOfMemoryError(記憶體溢出)。

每個線程對應着一個虛拟機棧,是以虛拟機棧也是線程私有的。

3.本地方法棧

本地方法棧在作用,運作機制,異常類型等方面都與虛拟機棧相同,唯一的差別是:虛拟機棧是執行Java方法的,而本地方法棧是用來執行native方法的,在很多虛拟機中(如Sun的JDK預設的HotSpot虛拟機),會将本地方法棧與虛拟機棧放在一起使用。

本地方法棧也是線程私有的。

4.堆區

在JVM所管理的記憶體中,堆區是最大的一塊,堆區也是Java GC機制所管理的主要記憶體區域,堆區由所有線程共享,在虛拟機啟動時建立。堆區的存在是為了存儲對象執行個體和數組。而對象的引用變量存儲在棧中,當沒有引用指向一個對象時,java提供了一種垃圾回收機制自動進行處理。

5.方法區

方法區(Method Area):在Java虛拟機規範中,将方法區作為堆的一個邏輯部分來對待,但事實上,方法區并不是堆(Non-Heap);方法區是各個線程共享的區域,用于存儲已經被虛拟機加載的類資訊(即加載類時需要加載的資訊,包括版本、field、方法、接口等資訊)、final常量、靜态變量、編譯器即時編譯的代碼等。

方法區在實體上也不需要是連續的,可以選擇固定大小或可擴充大小,并且方法區比堆還多了一個限制:可以選擇是否執行垃圾收集。一般的,方法區上執行的垃圾收集是很少的,這也是方法區被稱為永久代的原因之一(HotSpot),但這也不代表着在方法區上完全沒有垃圾收集,其上的垃圾收集主要是針對常量池的記憶體回收和對已加載類的解除安裝。

運作時常量池(Runtime Constant Pool)是方法區的一部分,用于存儲編譯期就生成的字面常量、符号引用、翻譯出來的直接引用(符号引用就是編碼是用字元串表示某個變量、接口的位置,直接引用就是根據符号引用翻譯出來的位址,将在類連結階段完成翻譯);運作時常量池除了存儲編譯期常量外,也可以存儲在運作時間産生的常量(比如String類的intern()方法,作用是String維護了一個常量池,如果調用的字元“abc”已經在常量池中,則傳回池中的字元串位址,否則,建立一個常量加入池中,并傳回位址)。

一般來說,一個Java的引用通路涉及到3個記憶體區域:JVM棧,堆,方法區。以最簡單的本地變量引用:Object obj = new Object()為例:

Object obj表示一個本地引用,存儲在JVM棧的本地變量表中,表示一個reference類型資料;

new Object()作為執行個體對象資料存儲在堆中;

堆中還記錄了Object類的類型資訊(接口、方法、field、對象類型等)的位址,這些位址所執行的資料存儲在方法區中;