1. 程式人生 > >深入理解JVM——Java虛擬機器記憶體模型

深入理解JVM——Java虛擬機器記憶體模型

開發十年,就只剩下這套架構體系了! >>>   

一、Java虛擬機器記憶體模型

Java虛擬機器記憶體資料區域由程式計數器(Program Counter Register)、Java虛擬機器棧(Java Virtual Machine Stacks)、本地方法棧(Native Method Stack)、Java堆(Java Heap)、方法區(Method Area)這幾部分組成;除此以外,還有一個直接記憶體,事實上這部分並不是虛擬機器執行時資料區的一部分,也不是Java虛擬機器規範中定義的記憶體區域,但是因為在JDK1.4新加入的NIO(New Input/Output)類,以及JDK1.8+後的Metaspace的關係,所以在討論JVM時也經常會被放到一起討論。

1.1 程式計數器(Program Counter Register)

  • 是一塊較小的記憶體空間,可以看作是當前執行緒所執行的位元組碼的行號指示器。
  • 每個執行緒都由一個獨立的程式計數器,以便執行緒切換後能恢復到正確的執行位置。
  • 若執行緒正在執行一個Java方法,則計數器記錄的是正在執行的虛擬機器位元組碼指令的地址;若是執行native方法,則計數器值為空。
  • 此記憶體區域是執行緒私有的,也是唯一一個在JVM規範中沒有規定任何OutOfMemoryError情況的區域。

1.2 Java虛擬機器棧(Java Virtual Machine Stacks)

  • 執行緒私有的,它的生命週期與執行緒相同。
  • 棧裡面存放的元素叫棧幀,每個方法從呼叫到執行結束,其實是對應一個棧幀的入棧和出棧。棧幀用於儲存執行方法時的一些資料,如區域性變量表(編譯期可知的各種基本資料型別、物件引用和returnAddress型別)、運算元棧(執行引擎計算時需要)、動態連結、方法出口等。
  • 這個區域規定了兩種異常:如果執行緒請求的棧深度大於虛擬機器所允許的深度,將丟擲StackOverflowError異常(如:將一個函式反覆遞迴自己,最終會出現這種異常);如果JVM棧可以動態擴充套件(大部分JVM是可以的),當擴充套件時無法申請到足夠記憶體則丟擲OutOfMemoryError異常

1.3 本地方法棧(Native Method Stack)

  • 本地方法棧與JVM棧的作用很相似,他們的區別在於虛擬機器棧是為虛擬機器執行Java方法服務,而本地方法棧是為虛擬機器使用到的Native方法服務。
  • 和JVM棧一樣,這個區域也會丟擲StackOverflowError和OutOfMemoryError異常。

1.4 Java堆(Java Heap)

  • Java堆是JVM所管理的記憶體中最大的一塊。它是被所有執行緒共享的一塊記憶體區域,在虛擬機器啟動時建立。
  • 此記憶體區域的唯一目的就是存放物件例項,幾乎所有的例項物件都是在這裡分配記憶體。(JIT編譯器並不是這麼絕對)。
  • Java堆(GC堆)是垃圾收集管理的主要區域。從記憶體回收的角度看,收集器基本都採用分代收集演算法,所以Java堆還可以細分為:新生代和老年代;再細緻分就是把新生代分為:Eden空間、From Survivor空間、To Survivor空間
  • 根據Java虛擬機器規範的規定,Java堆可以處於物理上不連續的記憶體空間中,只要邏輯上是連續的即可。
  • 如果在堆中沒有記憶體完成例項分配,並且堆也無法再擴充套件時,將會丟擲OutOfMemoryError異常。

1.5 方法區(Method Area)

  • 與Java堆一樣,是各個執行緒共享的記憶體區域。
  • 更願意稱它為“永久代”(Permanent Generation)。
  • 用於儲存已被虛擬機器載入的類資訊、常量、靜態變數、即時編譯器編譯後的程式碼等資料。
  • 這個區域主要是針對常量池回收,值得注意的是JDK1.7已經把常量池轉移到堆裡面了
  • 當方法區無法滿足記憶體分配需求時,會丟擲OutOfMemoryError。

1.6 執行時常量池(Runtime Constant Pool)

  • 它是方法區的一部分。
  • 存放類中固定的常量資訊、方法引用資訊等,其空間從方法區域(JDK1.7後為堆空間)中分配。
  • Class檔案中除了有類的版本、欄位、方法、介面等描述資訊外,還有一項就是常量池(Constant Pool Table),用於存放編譯期生成的各種字面量和符號引用,這部分內容將在類載入後進入方法區的執行時常量池存放。但是Java語言並不要求常量一定只有編譯期預置入Class的常量池的內容才能進入方法區常量池,執行期間也可將新內容放入常量池(最典型的String.intern()方法)。
  • 當常量池無法在申請到記憶體時會丟擲OutOfMemoryError異常。