07.25 那些年,我們一起嫌棄過的Java內存模型(JMM)

上學時期,Java學起來非常枯燥,什麼JVM規範,Java語言規範,JMM(Java Memory Model)等之類的東西,不過掌握好這些,考試能考個好成績。工作後,感覺離JVM、JMM等漸行漸遠,而隨著工作年限的增加,感覺離JVM、JMM等越來越近,今天,就來談一談那些年,我們一起嫌棄過的Java內存模型(JMM)。

首先是JVM(Java虛擬機),在執行Java程序的過程中,會把它所管理的內存劃分為若干個不同的區域。這些區域都有各自的用途,以及創建和銷燬的時間,有的區域隨著虛擬機的啟動而存在,有些區域則是依賴用戶線程的啟動和結束而建立和銷燬。先看一張大概JVM虛擬機運行時數據區的圖:

那些年,我們一起嫌棄過的Java內存模型(JMM)

其中運行時數據區域的劃分,也就構成了Java的內存模型:

那些年,我們一起嫌棄過的Java內存模型(JMM)

下面就一一介紹這些個區域的概念

1、程序計數器:是一塊較小的線程私有的內存空間,他的作用可以看做是當前線程所執行的字節碼的行號指示器。在虛擬機的概念模型裡,字節碼解釋器的工作就是通過改變這個計數器的值來選取下一條需要執行的字節碼指令,分支、循環、跳轉、異常處理、線程恢復等基礎功能都需要依賴這個計數器來完成。

如果線程正在執行的是一個Java方法,這個計數器記錄的是正在執行的虛擬機字節碼指令的地址;如果正在執行的是Native方法,這個計數器的值則為空。此內存區域是唯一一個在Java虛擬機規範中沒有規定任何OutOfMemoryError情況的區域。

2、Java虛擬機棧(JVM Stacks):也是線程私有的,它的生命週期和線程相同。虛擬機棧描述的是Java方法執行的內存模型:每個方法執行的時候都會創建一個棧幀用於存儲局部變量表、操作棧、動態鏈接、方法出口等信息,每一個方法被調用直至執行完畢,就對應著一個棧幀在虛擬機棧中從入棧到出棧的過程。

在Java虛擬機規範中,如果線程請求的棧深度大於虛擬機所允許的深度,將跑出StackOverflowError的異常;當前大部分虛擬機可以動態擴展,如果虛擬機棧動態擴展時無法申請到足夠的內存時,將會拋出OutOfMemoryError異常。

3、本地方法棧:與虛擬機棧所發揮的作用是非常相似的,只不過虛擬機棧為虛擬機執行Java方法來服務,而本地方法棧則為虛擬機使用到的Native方法服務,該內存區域也會拋出StackOverflowError異常和OutOfMemoryError異常。

4、Java堆(Java Heap):是Java虛擬機所管理內存中最大的一塊。Java堆是被所有線程共享的一塊內存區域,在虛擬機啟動時創建,此內存區域的唯一目的就是存放對象實例,幾乎所有的對象實例都在這裡分配內存。

為什麼要用“幾乎”呢?原本Java虛擬機規範中說:所有的對象實例都在這裡分配。但是隨著JIT編譯器的發展和逃逸分析技術的成熟,棧上分配、標量替換優化技術將會導致一些微妙的變化發生,所有的對象都分配到堆上也不是那麼絕對了。

那些年,我們一起嫌棄過的Java內存模型(JMM)

Java堆是垃圾收集器管理的主要區域,由於現在收集器基本都是採用分代收集算法,所以Java堆還可以細分為:新生代和老年代,新生代還分為Eden空間和兩個Survivor空間,每個空間都有自己的使命,這裡就不多介紹了。Java堆可以處於物理上不連續的內存空間中,只要邏輯上是連續的就行,如果在堆中已經沒有足夠的內存完成實例分配,並且堆也無法再擴展時,將會拋出OutOfMemoryError異常。Java堆的大小可以通過-Xms設置初始化大小,-Xmx設置可擴展最大的Java堆大小。

5、方法區:也是線程共享的一塊內存區域,用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據,也稱為永久代的區域。它的大小可以通過-XX:PermSize和-XX:MaxPermSize進行設置。

運行時常量池是方法區的一部分,Class文件除了有類的版本、字段、方法、接口等描述信息外,還有一項信息時常量池,用於存放編譯器生成的各種字面量和符號引用,這部分內容將在類加載後存放的方法區的運行時常量池中。當常量池無法再申請到內存時將會拋出OutOfMemoryError異常。

虛擬機運行起來,這行內存區域的使用情況都是可以被JDK中自帶的監控工具(jconsole、jvisualvm)所監控的,如下圖:

那些年,我們一起嫌棄過的Java內存模型(JMM)

那些年,我們一起嫌棄過的Java內存模型(JMM)

敲代碼猶如生產經營,而精通JVM、JMM就像高效管理,經營與管理完美結合,才能玩轉高司令賜予我們的Java語言!


分享到:


相關文章: