JVM 內存結構

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

  1. 程序計數器
  2. Java虛擬機棧
  3. 本地方法棧
  4. 方法區
    JVM內存結構

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

程序計數器(PC 寄存器)


程序計數器的定義

程序計數器是一塊較小的內存空間,是當前線程正在執行的那條字節碼指令的行號指示器;在虛擬機的概念模型裏,字節碼解釋器工作時,就是通過改變這個計數器的值來選取下一條需要執行的字節碼指令,分支、循環、跳準、異常處理、線程恢復等基本功能都需要依賴這個計數器來完成。若當前線程正在執行的是一個本地方法,那麼此時程序計數器爲Undefined。

程序計數器的作用

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

程序計數器的特點

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

Java 虛擬機棧(Java棧)


Java虛擬機棧的定義

Java虛擬機棧是描述Java方法運行過程的內存模型。
Java虛擬機棧會爲每一個即將運行的Java方法創建一塊叫做“棧幀”的區域,用於存放該方法運行過程中的一些信息,如:

  • 局部變量表
  • 操作數棧
  • 動態鏈接
  • 方法出口信息

  • 在這裏插入圖片描述

壓棧出棧的過程

當方法運行過程中需要創建局部變量時,就將局部變量的值存入棧幀中的局部變量表中。
Java 虛擬機棧的棧頂的棧幀是當前正在執行的活動棧,也是當前正在執行的方法,PC 寄存器也會指向這個地址。只有這個活動的棧幀的本地變量可以被操作數棧使用,當在這個棧幀中調用另一個方法,與之對應的棧幀又會被創建,新創建的棧幀壓入棧頂,變爲當前的活動棧幀。

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

Java虛擬機的特點

  • 局部變量表隨着棧幀的創建而創建,它的大小在編譯時確定,創建時只需分配實現規定的大小即可。在方法運行過程中,局部變量表的大小不會發生改變。
  • Java虛擬機棧會出現兩種異常:StackOverFlowError 和 OutOfMemoryError.
    • StackOverFlowError 若 Java 虛擬機棧的大小不允許動態擴展,那麼當前線程請求棧的深度超過當前 Java 虛擬機棧的最大深度時,拋出StackOverFlowError 異常
    • OutOfMemoryError 若允許動態擴展,那麼當前線程請求棧時內存用完了,無法再動態擴展時,拋出 OutOfMemoryError 異常。
  • Java 虛擬機棧也是線程私有的,隨着線程的創建而創建,隨着線程的結束而銷燬。

本地方法棧(C棧)

本地方法棧的定義

本地方法棧是爲 JVM 運行 Native 方法準備的空間,由於很多Native方法都是用 C 語言實現的,所以它通常又叫 C 棧。它與 Java 虛擬機棧的實現的功能類似,只不過本地方法棧是描述本地方法運行過程的內存模型。

棧幀變化的過程

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

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


堆的定義

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

堆的特點

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

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

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

方法區


方法區定義

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

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

方法區的特點

  • 線程共享。方法區是堆的一個邏輯部分,因此和堆一樣,都是線程共享的,整個虛擬機中只有一個方法區。
  • 永久代。方法區中的信息一般需要長期存在,而且它又是堆的邏輯分區,因此用堆的劃分方法。把方法區成爲“永久代”。
  • 內存回收效率低。方法區中的信息一般需要長期存在,回收一遍之後可能只有少量信息無效。主要回收目標是:堆常量池的回收;對類型的卸載。
  • Java 虛擬機規範對方法區的要求比較寬鬆。和堆一樣,允許固定大小,也允許動態擴展,還允許不實現垃圾回收。

運行時常量池

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

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

直接內存(堆外內存)

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

操作直接內存

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

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

直接內存與堆內存比較

直接內存申請空間耗費更高的性能
直接內存讀取 IO 的性能要優於普通的堆內存。
直接內存作用鏈: 本地 IO -> 直接內存 -> 本地 IO
堆內存作用鏈:本地 IO -> 直接內存 -> 非直接內存 -> 直接內存 -> 本地 IO

服務器管理員在配置虛擬機參數時,會根據實際內存設置-Xmx等參數信息,但經常忽略直接內存,使得各個內存區域總和大於物理內存限制,從而導致動態擴展時出現OutOfMemoryError異常。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章