Java虛擬機 - 內存區(qū)域

做有關 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ū)域

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 堆

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("");
        }
    }
}

運行以上代碼將會得到

Java 堆異常

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

方法區(qū)

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

HotSpot 虛擬機

上面介紹了 JVM 運行時數(shù)據(jù)區(qū),我們通過下面的圖再回顧一下

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 虛擬機》

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容