1. 程式人生 > 實用技巧 >JVM學習筆記(五、執行時資料區)

JVM學習筆記(五、執行時資料區)

目錄:

  • 簡介
  • 方法區(元空間)

簡介

執行時資料區分為兩類,一類是執行緒間共享的方法區和堆,另一類是執行緒私有的虛擬機器棧、本地方法棧以及程式計數器。

對於大多數應用來說,Java堆(Heap)是Java虛擬機器所管理的記憶體中最大的一塊,它是用來存放物件例項

  • 堆是被所有執行緒共享的記憶體區域,虛擬機器啟動時建立
  • 堆目的是存放物件例項,所有例項都在此分配記憶體。
  • 如果在堆中沒有記憶體完成例項的分配,並且堆也無法擴充套件時,丟擲OutOfMemoryError異常。

方法區(元空間)

方法區(元空間):它也是被各執行緒共享的區域,用於儲存已被虛擬機器載入的類資訊、常量、靜態變數、即時編譯器編譯後的程式碼

等資料。

相對而言,GC在這個區域不長出現,但並不是不會出現。此區域的GC的主要目標是常量池的回收、型別的解除安裝

當方法區無法滿足記憶體分配的需要時丟擲OutOfMemoryError異常

上面有提到即時編譯器編譯後的程式碼,是什麼意思呢?

首先我們要知道Java是解釋型語言(當然這是Java1.0前的版本,之後的版本已經優化,是解釋+編譯),其速度肯定是不如C這種編譯型的語言快。

那怎麼辦呢,SUN的做法就是,既然一行行的解釋執行慢,那我就把熱點位元組碼抽出來先編譯成可執行的機器碼,這樣就不會解釋重複的程式碼,那效率肯是會大幅提升的。

即時編譯器編譯後的程式碼正式上面說道的熱點程式碼編譯

,也就是JVM的優化JIT

Java棧是Java方法執行的記憶體模型,每個方法在執行的同時會建立一個棧幀用於儲存區域性變量表、運算元棧、動態連結、方法出口等資訊。

每個方法從呼叫直至執行完成的一系列過程都對應著一個棧幀在虛擬機器中入棧和出棧的過程。

棧的記憶體大小

  • 對Java 6來說,2位Sparc系統預設的棧記憶體是512k64位系統是1024k
  • 對Java 6來說,32位的X86 Solaris/linux系統預設棧記憶體是320k64位X86 Solaris/linux系統預設棧記憶體是1024k
  • 對Java 6來說,Windows中預設的棧記憶體是從java.exe中讀出來
    的。
  • 對Java 6來說,32位系統預設大小為320k64位系統為1024k

1、棧幀(Stack Frame):

棧幀由三部分組成:區域性變量表、運算元棧以及幀資料

棧幀的大小因區域性變量表和運算元棧而異。當JVM執行一個方法時,它會檢查class中的資料,以便確定一個方法執行時在區域性變量表和運算元棧中所需儲存的word size。

然後,JVM會為當前方法建立一個size相對應的棧幀,然後把它push到棧頂

注意:一個棧幀需要分配多少記憶體,在編譯的時候已經確定,不會受到程式執行期變數資料的影響,而僅僅取決於具體的虛擬機器實現。

2、區域性變量表(Local Variable Table):

用於儲存函式的引數以及區域性變數用的,區域性變量表中的變數只在當前函式呼叫中有效,當函式呼叫結束後,隨著函式棧幀的銷燬,區域性變量表也會隨之銷燬。區域性變量表在編譯期確定大小。

  • 編譯時就可以確定棧幀需要多大的區域性變量表,具體大小可在編譯後的Class檔案中看到。
  • 區域性變量表的容量以Variable Slot(變數槽)為最小單位,每個變數槽都可以儲存32位長度的記憶體空間
  • 在方法執行時,虛擬機器使用區域性變量表完成引數值到引數變數列表的傳遞過程。
    • 如果執行的是例項方法,那區域性變量表中第0位索引的Slot預設是用於傳遞方法所屬物件例項的引用,也就是this關鍵字。
    • 其餘引數則按照引數表順序排列,佔用從1開始的區域性變數Slot。
  • 基本型別資料以及引用returnAddress(返回地址)佔用一個變數槽long和double需要兩個

總結:

  • 棧幀需要的區域性大小是編譯時確定,可在位元組碼檔案檢視。
  • 區域性變量表最小容量單位是Variable Solt,每個槽可儲存32位長度的記憶體空間。
  • 方法執行傳參時,例項方法第0個槽是的佔用為this,其它引數依次往後。
  • 基本型別、應用型別、返回地址僅棧一個槽,long、double棧兩個。

3、運算元棧(Operand Stack):

主要用於儲存計算過程的中間結果,同時作為計算過程中變數臨時的儲存空間。只支援出棧入棧操作,同樣也可以在編譯期確定大小

  • 棧幀被建立時,操作棧是空的。操作棧的每個項可以存放JVM的各種型別資料,其中long和double型別(64位資料)佔用兩個棧深
  • 方法執行的過程中,會有各種位元組碼指令往運算元棧中寫入和提取內容,也就是出棧和入棧操作(與 Java 棧中棧幀操作類似)。
  • 操作棧呼叫其它有返回結果的方法時,會把結果push到棧上(通過運算元棧來進行引數傳遞)。

4:、動態連線:

每個棧幀都包含一個指向執行時常量池中該棧幀所屬方法的引用,持有這個引用是為了支援方法呼叫過程中的動態連結。

在類載入階段中的解析階段會將符號引用轉為直接引用,這種轉化也稱為靜態解析。另外的一部分將在執行時轉化為直接引用,這部分稱為動態連結。

舉個例子:

 1 public class Person {
 2 
 3     public static void main(String[] args) {
 4         Person person = new Male();
 5     }
 6 
 7 }
 8 
 9 class Male extends Person {
10 }

person引用在編譯時無法確認其型別,只有在執行期才能動態的獲取型別,這就是動態的連結引用如例項關係。

當然動態連結不僅僅只是多型,像反射等也是。

5、返回地址:

方法開始執行後,只有2種方式可以退出 :方法返回指令,異常退出。

6、幀資料區:

棧幀需要一些資料來支援常量池解析、正常方法返回和異常處理等。在幀資料區中儲存著訪問常量池的指標,方便程式訪問常量池。幀資料區的大小依賴於JVM的具體實現。