Java記憶體區域和記憶體模型

別惹豬兒蟲發表於2019-04-08

大綱

Java記憶體區域和記憶體模型

github倉庫地址

所有文章同步更新到github上的 Java—Notes專案,劃分的可能更清晰,我羅列出了目錄

如果覺得好可以點個star收藏,我會盡量一週更新一到兩篇文章,謝謝大兄弟們

重要

閱讀這篇文章前,一個重要的概念要弄清楚。

Java 記憶體區域和 Java 記憶體模型(JMM)是兩個概念

很多人,包括寫部落格的一些人,他們自己都搞不清楚這兩個啥意思,我搜出來文章很多將兩者混為一談的。

我用這個名字做標題也是讓很多不太清楚的朋友都能搜到這篇文章,然後搞清楚概念。這篇文章講 Java記憶體區域,如果你是想了解記憶體模型的話,可以看我的這篇文章 Java記憶體模型。當然如果你是面試的話,一般是問你Java記憶體區域(JVM執行時資料區),所以我先寫這一篇文章 Java記憶體模型(JMM)

Java記憶體區域

Java記憶體區域是這樣的:

Java記憶體區域和記憶體模型

記憶體模型

記憶體模型是這樣的

Java記憶體區域和記憶體模型

二者關係

引用《深入理解Java虛擬機器》裡的原話:"這裡所講的主記憶體、工作記憶體與本書第2章所講的Java記憶體區域中的堆,棧、方法區等並不是同一層次的記憶體劃分,這兩者基本上是沒有關係的,如果兩者一定要勉強對應起來,那從變數、主記憶體、工作記憶體的定義來看,主記憶體主要對應於Java堆中的物件例項資料部分,而工作記憶體則對應於虛擬機器棧中的部分割槽域"

記憶體區域劃分

Java記憶體區域和記憶體模型

Java執行時資料區可以分為兩類:

  • 執行緒私有
    • 虛擬機器棧
    • 本地方法棧(不是所有JVM都有,比如Hotspot就沒有)
    • 程式計數器
  • 執行緒共享
    • 方法區

執行緒私有

虛擬機器棧

概念

java虛擬機器棧是執行緒私有的,他的生命週期和執行緒相同。他描述的是Java方法執行的記憶體模型:每個方法在執行的同時都會建立一個棧幀用於儲存區域性變數表、運算元棧、動態連結、方法出口等資訊每個方法從呼叫知道執行完的過程,就對應這一個棧幀在虛擬機器棧中入棧到出棧的過程

什麼意思呢?我也用他的這個呼叫方法入棧的方式給你講吧

棧幀

他存放了以下重要資訊(部分):

  • 區域性變數表
  • 運算元棧
  • 動態連結
  • 方法出口
  • 等……..

我們還記得上面圖的記憶體模型嗎?我們把Java執行緒單獨拿出來,他的資料結構就成了這個樣子

Java記憶體區域和記憶體模型

假設我們現在有一個遞迴程式

public class Test {
    public static void main(String[] args) {
        Test test = new Test();
        test.recursive();
    }
    public void recursive(){
        recursive();
    }
}

複製程式碼

現在他在main執行緒中執行,那麼你可以把上圖的當前執行緒替換成main執行緒,然後他每呼叫一次recursive函式,他就把它壓入棧中。如果當前執行方法完,那麼他的棧幀就要出棧被拋棄。

上面的不會執行完,他會一直呼叫,直到消耗棧允許的最大深度,然後丟擲 Stack Overflow異常,這個我在後面異常的時候會講到

slot

我們還聽到很多部落格講述這個區域的時候有用到 slot這個概念,那個這個slot是什麼呢?

我們剛剛說到棧幀的資料結構,他儲存的重要資訊,其中有一個是區域性變數表,而這個區域性變數表也是由一個個小的資料結構組成,他就叫區域性變數空間(slot)。你也可以把它當成是一個空間大小的度量單位,就像位元組一樣,因為儲存的資料的大小也是用這個衡量的。

區域性變數表存放了編譯期可知的各種基本資料型別(boolean、byte、char、short、int、float、long、double)、物件引用(reference型別)和returnAddress型別。(我不知道書上是不是寫錯了,因為下圖的本地變數表中並沒有這個型別,而且在上圖的棧幀中,也是把這個和區域性變數表分開列出的,如果知道答案的請給我留言,非常感謝)

區域性變數表,就如下圖所示(這個是物件的訪問定位的其中一種方法,指標直接訪問物件,Java採用的是這種)。如果要看Java物件訪問的話,可以參考我的這篇文章,Java物件訪問。

Java記憶體區域和記憶體模型

我們看到 intshort等只佔用了一個 slot但是double佔用了兩個slot,(long沒有畫出,但他也佔用兩個slot)在java虛擬機器中確實是這樣,double和long型別比較特殊,在其他方面,儲存long和double的時候,別的資料一般使用一個單位的空間就行,他們就需要兩個。

異常

虛擬機器棧會丟擲兩種異常

  • Stack Overflow Error
  • OutOfMemoryError (也就是常說的OOM異常)

這兩種異常是從兩個方面說的,可以形象的說為 深度和寬度

還記得上面棧幀的圖和我的程式嗎?

當我啟動那個程式他一定會報出 Stack Overflow Error。 因為他是無限的遞迴,但是我的虛擬機器的深度是有限的(這個可以進行引數設定調整 分配引數:-Xss)。順便說一句,如果你要執行很多的執行緒,那麼你可能需要把棧的深度調小,因為總的就那麼大,而每個執行緒使用的是獨立大小

OOM異常的話,是由於如果虛擬機器棧可以動態擴充的話(大部分都可以擴充套件),如果擴充時無法申請到足夠的空間,那麼就會丟擲OOM異常

本地方法棧

概念

注意:有的虛擬機器中是將本地方法棧和虛擬機器棧合在一起的,比如Hotspot虛擬機器

本地方法棧和虛擬機器棧差不多,不過 Java虛擬機器棧為虛擬機器執行Java方法服務,而本地方法棧為虛擬機器使用到的Native方法服務。我們知道Java虛擬機器有一部分是用其他語言編寫的,比如C/C++,因此他有一部分是這些語言的類庫方法。

Navtive 方法是 Java 通過 JNI 直接呼叫本地 C/C++ 庫,可以認為是 Native 方法相當於 C/C++ 暴露給 Java 的一個介面,Java 通過呼叫這個介面從而呼叫到 C/C++ 方法。當執行緒呼叫 Java 方法時,虛擬機器會建立一個棧幀並壓入 Java 虛擬機器棧。然而當它呼叫的是 native 方法時,虛擬機器會保持 Java 虛擬機器棧不變,也不會向 Java 虛擬機器棧中壓入新的棧幀,虛擬機器只是簡單地動態連線並直接呼叫指定的 native 方法。

具體可以參考這篇博文 Java本地方法棧

異常

因為和Java虛擬機器棧類似,所以異常也是

  • Stack Overflow
  • OOM

程式計數器

概念

程式計數器是執行緒私有的,他是一塊較小的記憶體空間,他可以看作是當前執行緒執行的位元組碼的行號指令器。在虛擬機器的概念模型裡(僅僅是概念模型,不同虛擬機器的實現方式可能有更高效的方法),位元組碼直譯器工作時就是通過改變這個計數器的值來選取下一條需要執行的位元組碼指令,分支、迴圈、跳轉、異常處理等基礎功能都需要依賴這個計數器。

為什麼執行緒私有

Java虛擬機器的多執行緒是通過執行緒輪流切換並分配處理器執行時間的方式來實現的,在任何一個確定的時刻,一個處理器(對於多核處理器來說是一個核心)都只會執行一條執行緒中的指令、因此為了執行緒切換後能恢復到正確的執行位置,每條執行緒都需要有一個獨立的程式計數器,各條執行緒之間計數器互不影響,獨立儲存,我們稱這類記憶體區域為"執行緒私有"的記憶體。

特殊

他是Java虛擬機器規範中唯一一個沒有規定OOM異常情況的區域

執行緒共享

這裡是GC垃圾回收重點"照顧"的物件,可以參考我的博文:JVM垃圾回收

Java堆

概念

對於大所數應用來說,Java堆是Java虛擬機器所管理的記憶體中最大的一塊,也被執行緒共享。唯一的目的就是存放物件例項,幾乎所有的物件例項都在這裡分配記憶體

他還可以再細分為:新生代,老年代(基於分代垃圾回收演算法,可以參考我上面的博文連線)

異常

  • OOM

可以通過引數設定大小(-Xmx 和 -Xms)

方法區

概念

和Java堆一樣是執行緒共享的,它用於儲存已經被虛擬機器載入的:

  • 類資訊
  • 常量
  • 靜態變數
  • 即時編譯後的程式碼
  • 等…...

永久代

使用 Hotspot 虛擬機器的開發者來說,更習慣把方法區稱為"永久代",但是兩者並不等價,僅僅是Hotspot虛擬機器的設計團隊選擇把GC分代收集擴充套件到方法區。而且**永久代已經從JDK1.7開始逐漸移除,並在 JDK1.8之後完全被移除。**而且Hotspot之所以有這樣的情況是因為當時他們為了方便,懶得為方法區單獨寫記憶體管理演算法,而直接使用Java堆的管理演算法。

然後他們就為這個付出了代價,所以現在已經移除了這個永久代

異常

  • OOM

當方法區無法滿足記憶體分配需求

總結

  • java記憶體區域(或者成為記憶體結構或者執行時資料區)和java記憶體模型(JMM)不是同一個概念

  • 記憶體區域劃分

    • 執行緒私有

      • Java虛擬機器棧
      • 本地方法棧(不是每個JVM都有)
      • 程式計數器
        • 唯一一個沒有OOM異常的地方
    • 執行緒共享

      • Java堆
      • 方法區
        • 永久代不等同方法區,在JDK1.8已經被移除

相關文章