做有關 Java 程序的開發(fā)一定知道Java的內存自動管理,不用再像 C/C++ 程序那樣手動控制(分配和釋放)內存。開發(fā)人員可以把更多的精力放到業(yè)務的開發(fā)上面,但是這種內存的自動管理功能有利也有弊,當程序出現(xiàn)內存泄漏或內存溢出的問題時,如果不熟悉 Java 虛擬機(JVM - Java Virtual Machine)的內存模型,往往很難排查問題的根源。
掌握 Java 虛擬機的相關知識可以說是中級程序員必備的一項技能。另一方面,Java 虛擬機作為面試官的必問題目,熟悉相關的知識也是不錯的充電和儲備。
本文包括了 Java 虛擬機的內存區(qū)域和有關異常的內容。
運行時數(shù)據(jù)區(qū)
JVM 在運行的時候會把祂管理的內存劃分為幾個不同的數(shù)據(jù)區(qū)域,這些區(qū)域都有各自的用于,以及創(chuàng)建和銷毀時間。
根據(jù)《Java 虛擬機規(guī)范》的定義,JVM 管理的內存將包括以下幾個區(qū)域

程序計數(shù)器
程序計數(shù)器是一塊較小的內存空間,可以看作是當前 線程 所執(zhí)行的字節(jié)碼的 行號 指示器。這句話不太好懂,個人理解,程序計數(shù)器其實就是用來記錄 每個線程 執(zhí)行到什么地方了,程序計數(shù)器是 線程私有 的,及內個線程擁有自己的內存空間,如上圖所示,程序計數(shù)器屬于 線程隔離數(shù)據(jù)區(qū)。
- 如果線程正在執(zhí)行的是一個 Java 方法,這個計數(shù)器記錄的是正在執(zhí)行的虛擬機字節(jié)碼指令的地址
- 如果線程正在執(zhí)行的是一個本地(Native)方法,這個計數(shù)器的值則為 未定義(Undefined)
Java 虛擬機棧
和程序計數(shù)器一樣,Java 虛擬機棧也是 線程私有 的,它的生命周期與線程相同,隨著線程的創(chuàng)建而創(chuàng)建,銷毀而銷毀。
虛擬機棧描述的是 Java方法 執(zhí)行的內存模型:每個方法在執(zhí)行的同時都會創(chuàng)建一個 棧幀(Stack Frame) 用于存儲 局部變量表、操作數(shù)棧、動態(tài)鏈接、方法出口 等信息。每個方法從調用到執(zhí)行完成的過程,就對應一個 棧幀 在 虛擬機棧 中入棧和出棧的過程。
該內存區(qū)域會拋出兩種異常:
- 如果線程請求的棧深度大于虛擬機允許的深度,將拋出 StackOverflowError 異常。
- 如果虛擬機棧可以動態(tài)擴展,如果擴展時無法申請到足夠的內存,將拋出 OutOfMemoryError 異常。
下面通過代碼來演示
public class StackOverflowTest {
public static void main(String[] args) {
new Test().stackOverflow();
}
private static class Test {
public void stackOverflow() {
stackOverflow();
}
}
}
運行以上代碼將會得到 java.lang.StackOverflowError
本地方法棧
本地方法棧 與 Java 虛擬機棧非常相似,只不過不是執(zhí)行的 Java 方法,而是 本地方法。最常見的 JVM 實現(xiàn) HotSpot,將 Java虛擬機棧 和 本地方法棧 合二為一,即為同一內存區(qū)域。
Java 堆

- 堆(Heap) 應該是大家接觸的最多一個內存區(qū)域,也是 JVM 中管理內存最大的一塊。
- 堆 是 線程共享 的內存區(qū)域,即每個線程都可以訪問到該內存區(qū)域。
- 堆 的唯一作用就是用來存放 Java 程序中的 對象(實例)
- 堆 也是 垃圾收集器 管理的主要區(qū)域,有時也會被稱為 GC堆
- 堆還可以劃分為:新生代 和 老年代
- 新生代還可以劃分為:Eden 空間、From Survivor 空間 和 To Survivor 空間
- 堆 中還可以劃分出多個 線程私有 的分配緩沖區(qū)(Thread Local Allocation Buffer,TLAB)
- 劃分空間的作用時為了更有效的進行垃圾回收,不論怎樣劃分,堆 內存空間里面存儲的都是 對象(實例)
- 如果堆中沒有內存再分配新的對象時,會拋出 OutOfMemoryError 異常
下面通過代碼來演示
public class OutOfMemoryTest {
public static void main(String[] args) {
List<String> strings = new ArrayList<>();
while (true) {
strings.add("");
}
}
}
運行以上代碼將會得到

注意:異常錯誤信息中顯示了內存溢出的區(qū)域為:Java heap space 即 Java 堆內存空間
方法區(qū)

- 與 堆 一樣 方法區(qū) 也是 線程共享 的內存區(qū)域
- 該區(qū)域存儲 已加載的類信息、常亮、靜態(tài)變量、即時編譯器(JIT)編譯后的代碼 等數(shù)據(jù)
- 當方法區(qū)中沒有內存再分配新的對象時,會拋出 OutOfMemoryError 異常
- 方法區(qū)中有一部分是 運行時常量池,該區(qū)域用于存放編譯時期生成的各種字面量和符號引用,這部分內容將在類加載以后存放于方法區(qū)的 運行時常量池 中
HotSpot 虛擬機
上面介紹了 JVM 運行時數(shù)據(jù)區(qū),我們通過下面的圖再回顧一下

這些都是 JVM 規(guī)范所定義的,接下來說一說業(yè)界用的最多的 JVM 實現(xiàn) -- HotSpot 的相關內容
關于如何查看使用的是什么 JVM 可以使用如下命令
java -version
如在 Windows 系統(tǒng)下會打印出以下內容
$ java -version
java version "1.8.0_191"
Java(TM) SE Runtime Environment (build 1.8.0_191-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.191-b12, mixed mode)
在 Linux 系統(tǒng)下會打印出以下內容
$ java -version
openjdk version "1.8.0_171"
OpenJDK Runtime Environment (build 1.8.0_171-b10)
OpenJDK 64-Bit Server VM (build 25.171-b10, mixed mode)
資料來源:周志明 ——《深入理解 Java 虛擬機》