對於 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字節的整數倍