1. 程式人生 > >JVM系列分析- 記憶體模型

JVM系列分析- 記憶體模型

JVM的記憶體模型是java語言繞不開的一個話題。要進行java的效能調優,首先就要了解其記憶體模型。在諸多的面試筆試中,這也是很多面試官會考察的內容。

本篇文章簡單介紹JVM記憶體模型的概念,結構和對應的引數設定,並根據具體的程式碼案例講解一下記憶體分配情況。

1.JVM記憶體結構

由圖可以較為清楚的看到,JVM的記憶體空間分為3大部分,分別是堆記憶體、方法區和棧記憶體。其中棧記憶體可以再細分為java虛擬機器棧和本地方法棧。堆記憶體可以劃分為新生代和老年代。新生代中還可以再次劃分為Eden區、From Survivor區和To Survivor區。劃分出來的各個區,分別儲存不同的資料,並且在一般情況下,其採取的記憶體回收策略也是不同的。

2.JVM記憶體區域功能

通過上圖可以看到JVM執行時的資料區:

2.1 堆記憶體

堆記憶體是JVM記憶體模型中最大的一塊區域,被所有執行緒共享,是在JVM啟動時候進行建立的。幾乎所有的物件的空間分配都是在堆記憶體上進行分配的。

考慮到JVM的記憶體回收機制,堆記憶體可以劃分為新生代和老年代兩個區域(預設新生代與老年代的空間大小為1:2)。新生代可以再劃分為Eden區、From Survivor區和To Survivor區(三者比例為8:1:1)。幾乎所有的新物件的建立都是在Eden區進行的。在垃圾回收(GC)過程中,Eden中的活躍物件會被轉移到Survivor區,當再到達一定的年齡(經歷過的Minor GC的次數),會被轉移到老年代中。

堆可以處於物理上不連續的記憶體空間中,但是需要滿足邏輯上的連續。在實現時,可以實現成固定大小的,也可以是可擴充套件的,不過當前主流的虛擬機器都是按照可擴充套件來實現的

2.2 方法區

方法區又被成為永久代(HotSpot虛擬機器的設計團隊選擇把GC分代收集擴充套件至方法區),同樣也是被所有的執行緒共享的。該區域中主要儲存類的資訊、靜態變數、常量和即時編譯器編譯後的程式碼等資料。

JDK1.7中,已經把放在永久代的字串常量池移到堆中。JDK1.8撤銷永久代,引入元空間。

方法區不需要連續的記憶體,可以選擇固定大小或者可擴充套件。並且還可以選擇不實現垃圾收集。相對而言,垃圾收集行為在這個區域是比較少出現的,但並非資料進入了方法區就如永久代的名字一樣“永久”存在了。這個區域的記憶體回收目標主要是針對常量池的回收和對型別的解除安裝,一般來說這個區域的回收“成績”比較難以令人滿意,尤其是型別的解除安裝,條件相當苛刻,但是這部分割槽域的回收確實是有必要的。當方法區無法滿足記憶體分配需求時,將丟擲OutOfMemoryError異常。

2.3 程式計數器

程式計數器是用於標識當前執行緒執行的位元組碼檔案的行號指示器。多執行緒情況下,每個執行緒都具有各自獨立的程式計數器,所以該區域是非執行緒共享的記憶體區域。

當執行java方法時候,計數器中儲存的是位元組碼檔案的行號;當執行Native方法時,計數器的值為空。

2.4 Java棧

java棧是執行緒私有的記憶體區域,其中儲存的是棧幀。對於一個java的方法,其開始呼叫,則會建立一個棧幀,儲存到java棧中;當該方法執行完成,則對應的是出棧的過程。

如果方法methodOne方法呼叫了methodTwo,那麼methodOne就會先入棧建立一個棧楨,接著methodTwo再入棧成為棧頂(假設沒有其他的方法執行),methodTwo執行完先出棧,接著methodOne執行完出棧。

每個棧幀中,儲存執行對應方法所必須的資訊,主要包含區域性變量表,操作棧,動態連線和方法出口等資訊。

區域性變量表中,可以存放的資料有8種基本資料型別(boolean,byte,char,short,int,float,long,double),物件引用和returnAddress型別。其中long和double因為是64位,會佔用兩個區域性變數的空間。

在Java虛擬機器規範中,對這個區域規定了兩種異常狀況:如果執行緒請求的棧深度大於虛擬機器所允許的深度(比如遞迴呼叫的時候),將丟擲StackOverflowError異常;如果虛擬機器棧可以動態擴充套件(當前大部分的Java虛擬機器都可動態擴充套件,只不過Java虛擬機器規範中也允許固定長度的虛擬機器棧),當擴充套件時無法申請到足夠的記憶體時會丟擲OutOfMemoryError異常。

2.5 本地方法棧

本地方法棧也是執行緒私有的記憶體區域,與java棧比較相似,不同之處在於該區域主要是儲存Native方法相關的資料。Native方法是非Java語言編寫的方法。

與虛擬機器棧一樣,本地方法棧區域也會丟擲StackOverflowError和OutOfMemoryError異常。

3.JVM記憶體引數設定

  • -Xms設定堆的最小空間大小。
  • -Xmx設定堆的最大空間大小。
  • -XX:NewSize設定新生代最小空間大小。
  • -XX:MaxNewSize設定新生代最大空間大小。
  • -XX:PermSize設定永久代最小空間大小。
  • -XX:MaxPermSize設定永久代最大空間大小。
  • -Xss設定每個執行緒的堆疊大小。

    4.程式碼案例講解

    使用如下的一段簡單程式碼案例,說明一下各個記憶體區域中儲存的資訊。

  • 堆中進行物件的空間分配,比如Hashtable物件和String物件。

  • 方法中儲存類資訊(TestJVM),方法(put方法,print方法,test方法)和靜態變數(NUM)

  • java棧中儲存物件引用(score)

    5.參考資料

    http://www.ityouknow.com/jvm/2017/08/25/jvm-memory-structure.html

http://blog.csdn.net/liyantianmin/article/details/50704434