JVM 內存模型

JVM 內存模型

Java 虛擬機的內存空間分爲 5 個部分:算法

  • 程序計數器
  • Java 虛擬機棧
  • 本地方法棧
  • 方法區

jvm-memory-model

JDK 1.8 同 JDK 1.7 比,最大的差異就是:元數據區取代了永久代。元空間的本質和永久代相似,都是對 JVM 規範中方法區的實現。不過元空間與永久代之間最大的區別在於:元數據空間並不在虛擬機中,而是使用本地內存。服務器

程序計數器(PC 寄存器)

程序計數器的定義

程序計數器是一塊較小的內存空間,是當前線程正在執行的那條字節碼指令的地址。若當前線程正在執行的是一個本地方法,那麼此時程序計數器爲Undefined多線程

程序計數器的做用

  • 字節碼解釋器經過改變程序計數器來依次讀取指令,從而實現代碼的流程控制。
  • 在多線程狀況下,程序計數器記錄的是當前線程執行的位置,從而當線程切換回來時,就知道上次線程執行到哪了。

程序計數器的特色

  • 是一塊較小的內存空間。
  • 線程私有,每條線程都有本身的程序計數器。
  • 生命週期:隨着線程的建立而建立,隨着線程的結束而銷燬。
  • 是惟一一個不會出現OutOfMemoryError的內存區域。

Java 虛擬機棧(Java 棧)

Java 虛擬機棧的定義

Java 虛擬機棧是描述 Java 方法運行過程的內存模型。jvm

Java 虛擬機棧會爲每個即將運行的 Java 方法建立一塊叫作「棧幀」的區域,用於存放該方法運行過程當中的一些信息,如:性能

  • 局部變量表
  • 操做數棧
  • 動態連接
  • 方法出口信息
  • ......

jvm-stack

壓棧出棧過程

當方法運行過程當中須要建立局部變量時,就將局部變量的值存入棧幀中的局部變量表中。spa

Java 虛擬機棧的棧頂的棧幀是當前正在執行的活動棧,也就是當前正在執行的方法,PC 寄存器也會指向這個地址。只有這個活動的棧幀的本地變量能夠被操做數棧使用,當在這個棧幀中調用另外一個方法,與之對應的棧幀又會被建立,新建立的棧幀壓入棧頂,變爲當前的活動棧幀。線程

方法結束後,當前棧幀被移出,棧幀的返回值變成新的活動棧幀中操做數棧的一個操做數。若是沒有返回值,那麼新的活動棧幀中操做數棧的操做數沒有變化。code

因爲Java 虛擬機棧是與線程對應的,數據不是線程共享的,所以不用關心數據一致性問題,也不會存在同步鎖的問題。

Java 虛擬機棧的特色

  • 局部變量表隨着棧幀的建立而建立,它的大小在編譯時肯定,建立時只需分配事先規定的大小便可。在方法運行過程當中,局部變量表的大小不會發生改變。
  • Java 虛擬機棧會出現兩種異常:StackOverFlowError 和 OutOfMemoryError。對象

    • StackOverFlowError

若 Java 虛擬機棧的大小不容許動態擴展,那麼當線程請求棧的深度超過當前 Java 虛擬機棧的最大深度時,拋出 StackOverFlowError 異常。blog

  • OutOfMemoryError

若容許動態擴展,那麼當線程請求棧時內存用完了,沒法再動態擴展時,拋出 OutOfMemoryError 異常。

  • Java 虛擬機棧也是線程私有,隨着線程建立而建立,隨着線程的結束而銷燬。
出現 StackOverFlowError 時,內存空間可能還有不少。

本地方法棧(C 棧)

本地方法棧的定義

本地方法棧是爲 JVM 運行 Native 方法準備的空間,因爲不少 Native 方法都是用 C 語言實現的,因此它一般又叫 C 棧。它與 Java 虛擬機棧實現的功能相似,只不過本地方法棧是描述本地方法運行過程的內存模型。

棧幀變化過程

本地方法被執行時,在本地方法棧也會建立一塊棧幀,用於存放該方法的局部變量表、操做數棧、動態連接、方法出口信息等。

方法執行結束後,相應的棧幀也會出棧,並釋放內存空間。也會拋出 StackOverFlowError 和 OutOfMemoryError 異常。

若是 Java 虛擬機自己不支持 Native 方法,或是自己不依賴於傳統棧,那麼能夠不提供本地方法棧。若是支持本地方法棧,那麼這個棧通常會在線程建立的時候按線程分配。

堆的定義

堆是用來存放對象的內存空間,幾乎全部的對象都存儲在堆中。

堆的特色

  • 線程共享,整個 Java 虛擬機只有一個堆,全部的線程都訪問同一個堆。而程序計數器、Java 虛擬機棧、本地方法棧都是一個線程對應一個。
  • 在虛擬機啓動時建立。
  • 是垃圾回收的主要場所。
  • 進一步可分爲:新生代(Eden區 From Survior To Survivor)、老年代。

不一樣的區域存放不一樣生命週期的對象,這樣能夠根據不一樣的區域使用不一樣的垃圾回收算法,更具備針對性。

堆的大小既能夠固定也能夠擴展,但對於主流的虛擬機,堆的大小是可擴展的,所以當線程請求分配內存,但堆已滿,且內存已沒法再擴展時,就拋出 OutOfMemoryError 異常。

Java 堆所使用的內存不須要保證是連續的。而因爲堆是被全部線程共享的,因此對它的訪問須要注意同步問題,方法和對應的屬性都須要保證一致性。

方法區

方法區的定義

Java 虛擬機規範中定義方法區是堆的一個邏輯部分。方法區存放如下信息:

  • 已經被虛擬機加載的類信息
  • 常量
  • 靜態變量
  • 即時編譯器編譯後的代碼

方法區的特色

  • 線程共享。

方法區是堆的一個邏輯部分,所以和堆同樣,都是線程共享的。整個虛擬機中只有一個方法區。

  • 永久代。

方法區中的信息通常須要長期存在,並且它又是堆的邏輯分區,所以用堆的劃分方法,把方法區稱爲「永久代」。

  • 內存回收效率低。

方法區中的信息通常須要長期存在,回收一遍以後可能只有少許信息無效。主要回收目標是:對常量池的回收;對類型的卸載。

  • Java 虛擬機規範對方法區的要求比較寬鬆。

和堆同樣,容許固定大小,也容許動態擴展,還容許不實現垃圾回收。

運行時常量池

方法區中存放:類信息、常量、靜態變量、即時編譯器編譯後的代碼。常量就存放在運行時常量池中。

當類被 Java 虛擬機加載後, .class 文件中的常量就存放在方法區的運行時常量池中。並且在運行期間,能夠向常量池中添加新的常量。如 String 類的 intern() 方法就能在運行期間向常量池中添加字符串常量。

直接內存(堆外內存)

直接內存是除 Java 虛擬機以外的內存,但也可能被 Java 使用。

操做直接內存

在 NIO 中引入了一種基於通道和緩衝的 IO 方式。它能夠經過調用本地方法直接分配 Java 虛擬機以外的內存,而後經過一個存儲在堆中的DirectByteBuffer對象直接操做該內存,而無須先將外部內存中的數據複製到堆中再進行操做,從而提升了數據操做的效率。

直接內存的大小不受 Java 虛擬機控制,但既然是內存,當內存不足時就會拋出 OutOfMemoryError 異常。

直接內存與堆內存比較

  • 直接內存申請空間耗費更高的性能
  • 直接內存讀取 IO 的性能要優於普通的堆內存。
  • 直接內存做用鏈: 本地 IO -> 直接內存 -> 本地 IO
  • 堆內存做用鏈:本地 IO -> 直接內存 -> 非直接內存 -> 直接內存 -> 本地 IO
服務器管理員在配置虛擬機參數時,會根據實際內存設置 -Xmx等參數信息,但常常忽略直接內存,使得各個內存區域總和大於物理內存限制,從而致使動態擴展時出現 OutOfMemoryError異常。
相關文章
相關標籤/搜索