一文帶你瞭解 JVM 的內存區域

對於 Java 程序員來講,在 JVM 自動內存管理機制的幫助下,再也不須要爲每個 new 操做去寫對應的 delete/free 代碼,不容易出現內存泄露和內存溢出的問題。不過正因如此,若是不瞭解虛擬機是怎樣使用內存的,一旦出現內存泄露和內存溢出的問題,那麼排查錯誤將會很是艱難。java

一. 內存區域


虛擬機在執行 Java 程序的過程當中會把它管理的內存劃分爲若干個不一樣的數據區域,這些區域各司其職程序員

1. 線程私有

下面這 3 個區域都是線程私有的區域,每一個線程獨佔一份安全

(1)程序計數器

  • 當前線程所執行的字節碼的行號指示器
  • 經過改變計數器的值來選取下一條執行的字節碼指令
  • 幫助完成分支,循環,跳轉,異常處理,線程恢復等基礎功能
  • 多線程環境中,爲了正常完成線程的切換,使得各個線程能恢復到正確的執行位置,所以每條線程都須要一個程序計數器
  • 惟一一個不會出現 OutOfMemoryError 狀況的區域

(2)虛擬機棧

  • 每一個方法執行時都會建立一個棧幀,當方法被調用,棧幀入棧,方法執行完成,出棧
  • 每一個棧幀存儲局部變量表,操做棧,動態連接,方法出口等
  • 局部變量表存儲了當前方法的局部變量,包括基本數據類型,對象引用(指針)以及 returnAddress 類型(指向一條字節碼指令的地址)
  • 可能出現的兩種異常: StackOverflowError:棧溢出,線程請求的棧深度大於虛擬機容許的深度
    OutOfMemoryError:內存溢出,若是虛擬機棧能夠動態擴展,那麼若是擴展時沒法申請到足夠的內存,則拋出異常

(3)本地方法棧

  • 做用,運行機制,異常類型等與虛擬機棧相同
  • 爲 native 方法服務
  • 不少虛擬機中,本地方法棧和虛擬機棧合二爲一

2. 線程共享

下面兩個爲線程共享的區域bash

(4)堆

  • 虛擬機管理的內存中最大的一塊
  • 存放對象實例
  • 垃圾回收器管理的主要區域,也被稱爲 GC 堆,並所以能夠細分爲新生代和老年代
  • 能夠處於不連續的空間中
  • 當沒有內存完成實例分配,堆也沒法擴展時,拋出 OutOfMemoryError

(5)方法區

  • 存儲已被虛擬機加載的類信息,常量,靜態變量,即時編譯器編譯後的代碼等
  • 垃圾回收行爲在這個區域不多出現,所以也被稱做爲「永久代」

值得注意的是,從 Java 8 開始,方法區被移除,取而代之的是一個叫元空間(Metaspace)的區域多線程

(6)運行時常量池

  • Java 6 及以前屬於方法區的一部分;Java 7 後被移入堆區域
  • 存放編譯器生產的各類字面量和符號引用,在類加載時期存入運行時常量池

二. 瞭解 Java 對象


1. 對象的建立

對象的建立過程當中有如下幾大步驟:佈局

(1)類加載檢查

當虛擬機遇到一個 new 指令,說明要建立對象了;但在建立對象以前,會先去檢查這個類是否已經加載過,解析和初始化過,若是沒有,則先執行類加載過程spa

(2)分配內存

在堆中劃分出一塊肯定大小的內存,分配方式有兩種線程

分配方法:指針

  • 指針碰撞:使用這種方法,堆內存必須是規整的(用過的放一邊,空閒的放一邊),而後中間放一個指針做爲分界點。分配內存時只須要將指針挪一段與對象大小相等的距離便可
  • 空閒列表:若是堆內存不規整,就只能使用空閒列表法了。JVM 維護一個列表來記錄內存塊的使用狀況;分配時找到一塊足夠大的空間劃分給對象而後更新表記錄便可

保證線程安全:code

爲了保證線程安全,避免同一塊區域同時分配給多個對象,一般使用兩種方法

  • CAS:每當要寫入數據時,先比較當前值(工做內存)與主內存中的值是否一致,是則進行寫入,不然從新獲取值
  • TLAB:每一個線程預先分配一小塊內存,稱爲本地線程分配緩衝(TLAB);若是某個線程的 TLAB 用完,須要分配新的 TLAB,這些則須要進行同步鎖定

(3)初始化零值

將分配到的內存空間都初始化爲零值;這樣可使得對象在代碼中不賦初始值就直接使用

public class Person {
    int age;

    public static void main(String[] args) {
        Person person = new Person();
        System.out.println(person.age);
    }
}
複製代碼

相似這樣的狀況,age 被默認賦值爲 0

(4)設置對象頭

將類的自身運行時數據(HashCode,GC 分代年齡,鎖狀態標誌等);類型指針(指向類元數據,肯定是哪一個類的實例)存放在對象頭中

(5)執行 init 方法

前面幾步完成後,對於虛擬機,一個新對象已經產生了,但對於 java 程序,須要執行 init 方法後,一個真正的對象才徹底產生出來

二. 對象的內存佈局

堆裏面存放的是對象實例,對象實例由三個部分組成

  • 對象頭:存儲自身運行時數據(HashCode,GC 分代年齡,鎖狀態標誌等);類型指針,指向類元數據(肯定是哪一個類的實例)
  • 實例變量:記錄對象的各類屬性數據信息
  • 填充數據:用於對齊字節(JVM 對對象起始地址的字節數有要求,是8字節的整數倍
相關文章
相關標籤/搜索