天天看點

對JVM記憶體模型的一些了解

文章目錄

    • JVM(Java virtual machine)
      • 1.JVM記憶體模型(JDK8)
      • 2.記憶體區域分類
      • 3.各區域的介紹及其作用詳解
        • 3.1 程式計數器
        • 3.2 虛拟機棧
        • 3.3 本地方法棧
        • 3.4 堆
        • 3.5 方法區

JVM(Java virtual machine)

1.JVM記憶體模型(JDK8)

對JVM記憶體模型的一些了解

Java1.8的改動:

将永久代移除,取而代之的是元空間。

元空間不再與堆是連續的實體記憶體,而是改為使用本地記憶體(Native Method)。也就意味着隻要本地記憶體足夠,就不會出現OOM(OutOfMemory)的錯誤。

2.記憶體區域分類

對JVM記憶體模型的一些了解

線程私有的區域:程式計數器、虛拟機棧、本地方法棧

線程共享的區域:堆、方法區、直接記憶體

3.各區域的介紹及其作用詳解

3.1 程式計數器

程式計數器是一塊較小的記憶體空間,它的作用可以看做是目前線程所執行的位元組碼的行号的訓示器。

如果線程正在執行的是一個Java方法,程式計數器記錄正在執行的虛拟機位元組碼指令的位址;

如果正在執行的是Native方法,程式計數器的值為Undefined。

程式計數器的主要作用:

  • 在單個線程執行的過程中,位元組碼解釋器通過改變程式計數器來依次讀取指令,進而實作代碼的流程控制,如:順序執行、循環、異常執行。
  • 在多線程并發運作的情況下,程式計數器用于記錄目前線程執行的位置,進而保證線程切換的時候能夠從上一次的位置繼續工作。

值得注意的是,程式計數器是JVM虛拟機規範中唯一一個沒有規定OOM(OutOfMemory)Error的記憶體區域。

顯然,由于程式計數器儲存的是位元組碼指令的位址,是以它占用的空間為一個很小的定值。是以,這個區域不會出現OOM的情況。

程式計數器的生命周期同它所屬于的線程的生命周期。

3.2 虛拟機棧

虛拟機棧描述的是Java方法執行的記憶體模型:

每個方法在執行的同時都會建立一個棧幀,用于存儲局部變量表、操作數棧、動态連結、方法出口等資訊。每一個方法從調用到執行完成的過程,就是對應的棧幀在虛拟機棧中從入棧到出棧的過程。

對JVM記憶體模型的一些了解

局部變量表:

局部變量表是一組變量值存儲空間,用于存放方法參數和方法内定義的局部變量。

一個局部變量可以儲存一個類型為boolean、byte、char、short、int、float和reference類型的資料。reference類型表示對一個對象執行個體的引用。

操作數棧:

操作數棧是資料運算的地方,大多數指令都在操作數棧彈棧運算,然後結果壓棧。操作數棧可了解為虛拟機棧中的一個用于計算的臨時資料存儲區。

i++

:将局部變量表中的i壓入操作數棧,将局部變量表中i自增,然後取棧頂的i使用。

++i

:将局部變量表中的i自增,然後将i壓入操作數棧中,最後取棧頂的i使用。

這兩個操作分為多步,不能保證原子性。

如何使其具有原子性?使用循環CAS操作。

動态連結:

每個棧幀中包含一個在常量池中對目前方法的引用。

方法傳回位址:

方法退出的過程相當于彈出目前棧幀,退出可能有三種方式:

  • 傳回值壓入上層調用棧幀。
  • 異常資訊抛給能夠處理的棧幀。
  • PC計數器指向方法調用後的下一條指令。

虛拟機棧可能會出現兩種錯誤:

StackOverFlowError

OutOfMemoryError

  • StackOverFlowError

    :若虛拟機棧的記憶體大小不允許動态擴充,當線程請求棧的深度超過目前 Java 虛拟機棧的最大深度的時候,就抛出

    StackOverFlowError

  • OutOfMemoryError

    :若虛拟機棧的記憶體大小可以動态擴充,在虛拟機動态擴充棧的時候無法申請到足夠的記憶體空間,就會抛出

    OutOfMemoryError

3.3 本地方法棧

和虛拟機棧所發揮的作用非常相似。

差別是: 虛拟機棧為虛拟機執行 Java 方法服務,而本地方法棧則為虛拟機使用到的 Native 方法服務。

本地方法棧也會出現

StackOverFlowError

OutOfMemoryError

3.4 堆

Java 虛拟機所管理的記憶體中最大的一塊,Java 堆是所有線程共享的一塊記憶體區域,在虛拟機啟動時建立。

此記憶體區域的唯一目的就是存放對象執行個體,幾乎所有的對象執行個體以及數組都在這裡配置設定記憶體。

JDK1.7之後,字元串常量池從方法區被移動到了堆中。

Java 堆是垃圾收集器管理的主要區域,是以也被稱作GC 堆(Garbage Collected Heap)。

從垃圾回收的角度,由于現在收集器基本都采用分代垃圾收集算法,是以 Java 堆還可以細分為:新生代和老年代。

再細緻一點,新生代又可以分為Eden、From Survivor、To Survivor 空間等。

對JVM記憶體模型的一些了解

進一步劃分的目的是更好地回收記憶體,或者更快地配置設定記憶體。

預設新生代 ( Young ) 與老年代 ( Old ) 的比例的值為 1:2 ( 該值可以通過參數 –XX:NewRatio 來指定 )

預設eden : from : to = 8 : 1 : 1 ( 可以通過參數 –XX:SurvivorRatio 來設定 )

常見的堆設定:

-Xms:初始堆大小

-Xmx:最大堆大小

-XX:NewSize=n:設定年輕代大小

-XX:NewRatio=n:設定年輕代和年老代的比值

-XX:SurvivorRatio=n:年輕代中Eden區與兩個Survivor區的比值

3.5 方法區

方法區與Java堆一樣,是各個線程共享的記憶體區域。

它用于存儲已被虛拟機加載的類資訊、常量、靜态變量、即時編譯器編譯後的代碼等資料。

雖然Java虛拟機規範把方法區描述為堆的一個邏輯部分,但是它卻有一個别名叫做Non-Heap(非堆),目的應該是與Java堆區分開來。

JDK7之後,廢棄了永久代的概念,改用在本地記憶體中實作的元空間(Metaspace)來代替。

-XX:MetaspaceSize=N //設定 Metaspace 的初始大小

-XX:MaxMetaspaceSize=N //設定 Metaspace 的最大大小

JDK7之後,字元串常量池被從方法區拿到了堆中,但是運作時常量池還在方法區之中。

class常量池和運作時常量池:

class常量池簡介:

我們寫的每一個Java類被編譯後,就會形成一份class檔案;class檔案中除了包含類的版本、字段、方法、接口等描述資訊外,還有一項資訊就是常量池(constant pool table),用于存放編譯器生成的各種字面量(Literal)和符号引用(Symbolic References);

每個class檔案都有一個class常量池。

什麼是字面量和符号引用:

字面量包括:

1.文本字元串

2.八種基本類型的值

3.被聲明為final的常量等;

符号引用包括:

1.類和方法的全限定名

2.字段的名稱和描述符

3.方法的名稱和描述符。

運作時常量池

存在于方法區中,也就是class常量池被加載到記憶體之後的版本,不同之處是:

它的字面量可以動态的添加

(如使用String#intern())

,符号引用可以被解析為直接引用

JVM在執行某個類的時候,必須經過加載、連接配接、初始化,而連接配接又包括驗證、準備、解析三個階段。而當類加載到記憶體中後,jvm就會将class常量池中的内容存放到運作時常量池中,由此可知,運作時常量池也是每個類都有一個。在解析階段,會把符号引用替換為直接引用,解析的過程會去查詢字元串常量池,以保證運作時常量池所引用的字元串與字元串常量池中是一緻的。