天天看點

(二)、JVM的記憶體結構詳解

1.JVM記憶體結構

  • Java虛拟機在執行Java程式過程中會把他所管理記憶體區域劃分為若幹個不同的資料區域。分别為堆、虛拟機棧、本地方法棧、程式計數器、方法區。
  • 這些區域都有各自的用途,以及建立和銷毀的時間,堆和方法區是線程共享的是以他們随着虛拟機程序的啟動而存在,本地方法棧、虛拟機棧、程式計數器屬于線程私有,是以他們随着線程的建立和結束而建立和銷毀。
  • JVM記憶體結構如圖
    (二)、JVM的記憶體結構詳解

2. 各個記憶體區域詳解

2.1 程式計數器

程式計數器是目前線程所執行的位元組碼的行号訓示器,位元組碼解釋器的工作就是通過改變這個計數器的值,來選取下一條需要執行的位元組碼指令,分支、循環、跳轉、異常處理、線程恢複等基礎功能,都需要依賴這個計數器來完成。

主要特點為:

  • 程式計數器是JVM運作資料區中非常小的一塊記憶體區域,屬于線程私有,是目前線程所執行的位元組碼的行号訓示器。
  • 位元組碼解釋器通過改變程式計數器的值來選取下一條需要執行的位元組碼指令。
  • 每條線程都有自己獨立的程式計數器。
  • 如果執行帶有native方法時,計數器的值為空(undefined)。
  • 這部分記憶體區域是唯一一個不會抛出任務OutOfMemoryError的情況。

2.2 堆

  • 堆記憶體是JVM所管理的記憶體中最大的一塊。線程共享,在虛拟機啟動時就會被建立,并且僅有一個,主要是存放對象執行個體和數組。
  • 堆記憶體可以位于實體上不連續,但是邏輯上必須連續。如果堆中沒有記憶體完成執行個體的配置設定,并且堆也無法拓展時,則抛出OutOfMemoryError Java heap space。
  • 堆内部會劃分多個線程私有的配置設定緩沖區(Thread Local Allocation Buffer,TLAB)。
  • Jave堆主要用來存放對象執行個體和數組,也就是說我們代碼抓中通過new關鍵字建立出來的對象都會放在這裡,是以堆也就是垃圾回收器的主要營地了,是以堆還有一個别名稱之為“GC堆”。
  • 如下圖:JDK8之前,堆記憶體是由新生代(Young generation)+老年代(Old generation)+持久代(Permanent generation)組成,JDK8之後,持久代被廢棄,而是使用了本地記憶體的“元空間”,一個對象被建立以後首先被放到新生代的Eden記憶體中,其中持久代/元空間都是用來存放對象的黨發、變量等中繼資料資訊
    (二)、JVM的記憶體結構詳解

2.3 虛拟機棧

(二)、JVM的記憶體結構詳解

Java 棧也被稱之為虛拟機棧,也就是我們常說的棧,跟C語言的資料段中的棧類似。事實上,Java棧是Java方法執行的記憶體模型。為什麼會這麼說呢?接下來解釋下原因:

Java棧中存放的是一個個棧幀,每個棧幀都是一個調用的方法,在棧幀中包括局部變量表(Local Variables)、操作數棧(Operand Stack)、指向目前方法所屬的類到的運作時常量池的引用(Reference to runtine constant pool)、方法傳回位址(Return Address)和動态連結。當線程執行一個方法時,就會随之建立一個對應的棧幀,并将建立的棧幀壓棧。當方法執行完畢之後,便會進行出棧。是以可以得知,目前執行的方法所對應的棧幀必定位于Java棧的頂部。講到這裡,大家一定就明白了為什麼使用遞歸方法容易造成棧記憶體溢出了吧。

局部變量表:用來存儲方法中的局部變量(包括在方法中聲明的非靜态變量以及函數形參)。對于基本資料類型的變量,則直接存儲它的值,對于引用類型的變量,則存的是指向對象的引用。局部變量表的大小在編譯器就可以确定其大小了,是以在程式執行期間局部變量表的大小是不會改變的。

操作數棧:想必學過資料結構中的棧的朋友想必對表達式求值問題不會陌生,棧最典型的一個應用就是用來對表達式求值。想想一個線程執行方法的過程中,實際上就是不斷執行語句的過程,而歸根到底就是進行計算的過程。是以可以這麼說,程式中的所有計算過程都是在借助于操作數棧來完成的。

指向運作時常量池的引用:因為在方法執行的過程中有可能需要用到類的常量,是以必須要有一個引用指向運作時常量。

方法傳回位址:當一個方法執行完畢後,要傳回之前調用他的地方,是以棧中必須儲存一個方法的傳回位址。

2.4 方法區

(二)、JVM的記憶體結構詳解

方法區在JVM中是非常重要的一個區域,也是線程共享的,在方法區内,存儲了每個類的資訊(包括類的版本、字段的描述資訊、方法描述資訊、接口和父類等描述資訊、class檔案常量池機靜态常量池)、靜态常量、字元串常量池、運作時常量池等。

方法區中有個非常重要的部分就是運作時常量池,它是每一個類和接口的常量池的運作時表示形式,在類和接口被加載到JVM後,對應的運作時常量池就會被建立出來。當然并非Class檔案常量池中内容才會進入運作時常量池,在運作期間也可将新的常量放入運作時常量池,比如String和Inter方法。

在JVM規範中,沒有強制要求方法區必須實作垃圾回收。很多人習慣将方法區稱為“永久代”,是因為HotSpot虛拟機以永久代來實作方法區,進而JVM的垃圾收集器可以像管理堆區一樣管理這部分區域,進而不需要專門為這部分設計垃圾回收機制。不過自從JDK7之後,HotSpot虛拟機便将運作時常量池從永久代移除了,JDK8之後,這一塊區域為元空間(Meta Space)

2.5 本地方法棧

本地方法棧與Java棧的作用和原理非常相似。差別隻不過是Java棧是為執行Java方法服務的,而本地方法棧則是為執行本地方法(Native Method)服務的。在JVM規範中,并沒有對本地方發展的具體實作方法以及資料結構作強制規定,虛拟機可以自由實作它。在HotSopt虛拟機中直接就把本地方法棧和Java棧合二為一。