JVM內存模型

java運行時數據區域

java虛擬機所管理的內存包含如下幾個運行時數據區域,如圖:java

  • 程序計數器
  • java虛擬機棧
  • 本地方法棧
  • java堆
  • 方法區
    運行時數據區

1. 程序計數器

程序計數器是一塊比較小的內存空間,它是當前線程所執行的字節碼的行號指示器。算法

1.1 程序計數器的做用

因爲java虛擬機的多線程是經過線程輪流切換、分配處理器執行時間的方式來實現的,在任何一個肯定的時刻,一個處理器只會執行一條線程中的指令。所以爲了線程切換後能恢復到正確的執行位置,每條線程都須要有一個獨立的程序計數器,各個線程之間的計數器互不影響,獨立存儲,這類內存區域爲「線程私有」的內存。segmentfault

1.2 程序計數器的特色

  • 是一塊較小的內存空間。緩存

  • 線程私有,每條線程都有本身的程序計數器。bash

  • 生命週期:隨着線程的建立而建立,隨着線程的結束而銷燬。服務器

  • 是惟一一個不會出現OutOfMemoryError的內存區域。多線程


2 java虛擬機棧

2.1 java虛擬機棧定義

java虛擬機棧也是線程私有的,他的生命週期與線程想同。虛擬機棧描述的是java方法執行的線程內存模型。
jvm

每一個方法被執行的時候,java虛擬機都會同步建立一個戰爭,用於存儲在該方法運行過程當中的一些信息:佈局

  • 局部變量表
    • java虛擬機基本數據類型(boolean、byte、char、short、int、float、long、double);
    • 對象引用 ;
    • returnAddress ;
  • 操做數棧
  • 動態鏈接

    每一個棧幀都包含一個指向運行時常量池中該棧幀所屬方法的引用,持有這個引用是爲了支持方法調用過程當中的動態鏈接(Dynamic Linking)。post

  • 方法出口

    當一個方法開始執行後,只有兩種方式能夠退出,一種是遇到方法返回的字節碼指令;一種是碰見異常,而且 這個異常沒有在方法體內獲得處理。

  • .....

每一個方法被調用知道完畢的過程都對應着一個棧幀在虛擬機中從入棧到出棧的過程。

2.2 java虛擬機棧的特色

  • 局部變量表所需的內存空間在編譯期間完成分配,進入一個方法時,這個方法須要在棧幀中分配的局部變量空間是徹底肯定的,在方法運行期間不會改變局部變量表的大小。
  • Java虛擬機棧會出現兩種異常:StackOverflowErrorOutOfMemoryError
    • 若是線程請求的站深度大於虛擬機所容許的深度,將拋出StackOverflowError;
    • 若是java虛擬機站容量能夠動態擴展,當棧擴展時沒法申請到足夠的內存會拋出 OutOfMemoryError 異常。

3. 本地方法棧

本地方法棧與虛擬機所發揮的做用很類似,其區別在於虛擬機棧爲虛擬機執行java方法服務,而本地方法棧則是爲虛擬機使用到本地方法服務。

3.1 本地方法棧的特色

  • Java虛擬機棧會出現兩種異常:StackOverflowErrorOutOfMemoryError
    • 若是線程請求的站深度大於虛擬機所容許的深度,將拋出StackOverflowError;
    • 若是java虛擬機站容量能夠動態擴展,當棧擴展時沒法申請到足夠的內存會拋出 OutOfMemoryError 異常。

4. java堆

4.1 java堆定義

java堆是虛擬機所管理的內存中最大的一塊。java堆是被全部線程共享的一塊內存區域,在虛擬機啓動時建立。
此內存區域的惟一目的就是存放對象實例,java中「幾乎」全部的對象實例都在這裏分配內存。這裏使用「幾乎」是由於java語言的發展,及時編譯的技術發展,逃逸分析技術的日漸強大,棧上分配、標量替換等優化手段,使java對象實例都分配在堆上變得不那麼絕對。
Java堆是垃圾收集器管理的主要區域,所以不少時候也被稱作「GC堆」。從內存回收的角度來看,因爲如今收集器基本都採用分代收集算法(G1以後開始變得不同,引入了region,可是依舊採用了分代思想),Java堆中還能夠細分爲:新生代和老年代。再細緻一點的有Eden空間、From Survivor空間、ToSurvivor空間等。從內存分配的角度來看,線程共享的Java堆中可能劃分出多個線程私有的分配緩衝區(Thread Local Allocation Buffer,TLAB)。

4.2 堆區的調整

根據Java虛擬機規範的規定,Java堆能夠處於物理上不連續的內存空間中,只要邏輯上是連續的便可,就像咱們的磁盤空間同樣。在實現時,既能夠實現成固定大小的,也能夠在運行時動態地調整。

調整參數

經過設置以下參數,能夠設定堆區的初始值和最大值,好比:

-Xms256M -Xmx 1024M

其中 -X這個字母表明它是JVM運行時參數,ms是memory start的簡稱,中文意思就是內存初始值,mx 是 memory max的簡稱,意思就是最大內存。

PS:在一般狀況下,服務器在運行過程當中,堆空間不斷地擴容與回縮,會造成沒必要要的系統壓力 因此在線上生產環境中 JVM的Xms和 Xmx會設置成一樣大小,避免在GC 後調整堆大小時帶來的額外壓力。

4.3 OOM異常

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

/**
 * VM Args:-Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError
 */
public class HeapOOMTest {

    public static final int _1MB = 1024 * 1024;

    public static void main(String[] args) {
        List<Integer[]> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            Integer[] ints = new Integer[2 * _1MB];
            list.add(ints);
        }
    }
}

複製代碼
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid32372.hprof ...
Heap dump file created [7774077 bytes in 0.009 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at jvm.HeapOOMTest.main(HeapOOMTest.java:18)
複製代碼

5. 方法區

方法區是java堆同樣,是各個線程共享的內存區域,他用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼緩存等數據。

在 HotSpot JVM 中,永久代(永久代實現方法區)中用於存放類和方法的元數據以及常量池,好比Class和Method。每當一個類初次被加載的時候,它的元數據都會放到永久代中。
永久代是有大小限制的,所以若是加載的類太多,頗有可能致使永久代內存溢出, java.lang.OutOfMemoryError: PermGen,爲此咱們不得不對虛擬機作調優。
後來HotSpot放棄永久代(PermGen),jdk1.7版本中,HotSpot已經把本來放在永久代的字符串常量池、靜態變量等移出,到了jdk1.8,徹底廢棄了永久代,方法區移至元空間(Metaspace)。好比類元信息、字段、靜態屬性、方法、常量等都移動到元空間區。 元空間的本質和永久代相似,都是對JVM規範中方法區的實現。不過元空間與永久代之間最大的區別在於:元空間並不在虛擬機中,而是使用本地內存。所以,默認狀況下,元空間的大小僅受本地內存限制。

5.1 對應的JVM調參:

參數 做用
-XX:MetaspaceSize 分配給Metaspace(以字節計)的初始大小。
若是不設置的話,默認是20.79M,
這個初始大小是觸發首次 Metaspace Full GC 的閾值,例如 -XX:MetaspaceSize=256M
-XX:MaxMetaspaceSize 分配給Metaspace 的最大值,超過此值就會觸發Full GC,
此值默認沒有限制,但應取決於系統內存的大小。JVM會動態地改變此值。
可是線上環境建議設置,例如-XX:MaxMetaspaceSize=256M
-XX:MinMetaspaceFreeRatio 最小空閒比,當 Metaspace 發生 GC 後,會計算 Metaspace 的空閒比,
若是空閒比(空閒空間/當前 Metaspace 大小)小於此值,
就會觸發 Metaspace 擴容。默認值是 40 ,也就是 40%,
例如 -XX:MinMetaspaceFreeRatio=40
-XX:MaxMetaspaceFreeRatio 最大空閒比,當 Metaspace 發生 GC 後,會計算 Metaspace 的空閒比,
若是空閒比(空閒空間/當前 Metaspace 大小)大於此值,
就會觸發 Metaspace 釋放空間。默認值是 70 ,也就是 70%,
例如 -XX:MaxMetaspaceFreeRatio=70

建議將 MetaspaceSize 和 MaxMetaspaceSize 設置爲一樣大小,避免頻繁擴容。

5.2 OOM異常

若是方法區沒法知足新的內存分配需求時,將拋出 OutOfMemoryError 異常。

5.3 運行時常量池

運行時常量池是方法區的一部分,Class文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項信息是常量池表,用於存放編譯期間生成的各類字面量與符號引用,這部份內容將在類加載後存放到方法區的運行時常量池中。

方法區中存放:類信息、常量、靜態變量、即時編譯器編譯後的代碼。常量就存放在運行時常量池中。 當類被 Java 虛擬機加載後, .class文件中的常量就存放在方法區的運行時常量池中。並且在運行期間,能夠向常量池中添加新的常量。如 String 類的 intern() 方法就能在運行期間向常量池中添加字符串常量。


6. 直接內存

直接內存並非虛擬機運行時數據區的一部分。 在 NIO 中引入了一種基於通道和緩衝的 IO 方式。它能夠經過調用本地方法直接分配Java虛擬機以外的內存,而後經過一個存儲在堆中的DirectByteBuffer對象直接操做該內存,而無須先將外部內存中的數據複製到堆中再進行操做,從而提升了數據操做的效率。
直接內存的大小不受 Java 虛擬機控制,但既然是內存,當內存不足時就會拋出 OutOfMemoryError 異常。

6.1 直接內存與堆內存比較

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

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

參考

相關文章
相關標籤/搜索