1. 程式人生 > >JVM記憶體區域(一)—— 理論模型

JVM記憶體區域(一)—— 理論模型

引言

JVM在Java程式設計中起到了至關重要的作用,他遮蔽了底層細節,這樣只要有對應平臺的JVM版本,便可以執行自己的Java程式,從而實現了“一處編譯,到處執行”的特點。同時JVM的記憶體管理機制避免了類C語言中的delete/free操作,不容易出現記憶體洩漏和記憶體溢位問題。但是並不能因為JVM提供的便利性就可以認為不用深入瞭解他,恰恰相反,由於JVM這麼複雜的特性我們更應該將仔細的研究它。只有這樣,才能寫出更高效的程式碼,在出現記憶體問題的時候不至於毫無頭緒。而瞭解JVM的第一步,就是了解它的記憶體模型。

在正式開始Java虛擬機器記憶體區域的講解的時候,我有兩點小建議或者提示:

1.HotSpot是一種Java虛擬機器的具體實現,而下邊我們講的大部分都是Java虛擬機器規範中的內容,與具體實現略有差別。

2.當遇到不懂的地方,希望大家也不要放棄,我儘量避免沒有涉及的內容的出現,但是由於虛擬機器本身是個整體,所以也會稍微提及一些沒有涉及的內容,但是重在體會概要,不一定要全部弄懂。

執行時資料區

Java虛擬機器執行時資料區
Java虛擬機器執行時資料區

Java虛擬機器執行時資料區包括上圖的幾個部分,同時又可以根據是否執行緒共享分為兩類,下邊我們來逐個介紹這幾個區域:

程式計數器(執行緒私有

程式計數器是一塊小的記憶體空間,它可以看做是當前執行緒所執行的位元組碼的行號指示器。這個概念有點像我們在學習馮諾依曼計算機體系結構中的控制器。位元組碼直譯器工作時就是通過改變程式計數器的值來選取下一條需要執行的位元組碼指令,分支、迴圈、跳轉、異常處理、執行緒恢復等基礎功能的實現都需要依賴這個計數器。

Java虛擬機器的多執行緒是通過執行緒輪流切換並分配處理器執行時間的方式來實現的,因此,為了線上程切換後程序能夠恢復到正確的執行位置,每條執行緒都需要擁有一個自己的程式計數器,各個程式計數器之間互不影響,獨立儲存。所以這塊區域是每個執行緒“私有”的記憶體。

如果執行緒正在執行Java的方法,計數器記錄的就是正在執行的虛擬機器位元組碼的指令地址,如果正在執行的是Native方法,這個計數器的值為空。這塊記憶體區域是唯一一個不存在OutOfMemoryError情況的區域。

虛擬機器棧(執行緒私有

虛擬機器棧是描述Java方法執行的記憶體模型:每個方法在執行的時候都會建立一個叫做棧幀的東西(後邊還會講到,其實就是用來儲存方法資訊的東西),用來儲存區域性變量表、運算元棧、動態連結、方法出口等訊息。和我們平常認識的一樣,方法的呼叫時,就把方法壓棧,方法執行完成時,就把方法出棧。從我們現在瞭解的來看,更準確的說其實是棧幀的壓棧和出棧的過程。

在Java虛擬機器規範中,這個區域有兩種異常的情況:

1.執行緒請求棧的深度大於虛擬機器允許的深度,則丟擲StackOveflowError異常

2.如果虛擬機器可以動態擴充套件,如果擴充套件時無法申請到足夠的記憶體,則丟擲OutOfMemoryError異常

本地方法棧(執行緒私有

本地方法棧與虛擬機器棧的作用十分相似,從名字上我們基本上可以比較出差別來,本地方法棧是為Native方法服務的,虛擬機器棧是為Java方法服務的。虛擬機器規範中沒有規定怎麼實現它,所以有的虛擬機器(Sun 的 HotSpot 虛擬機器)直接就把本地方法棧和虛擬機器棧合二為一了。

本地方法棧同樣也會和虛擬機器一樣丟擲StackOverflowError和OutOfMemoryError異常。

Java堆(執行緒公有

Java堆是用來存放物件例項的,Java虛擬機器規範對堆的描述是:所有的物件例項都要在這裡分配記憶體。但是隨著技術的發展,這句話也變的不是這麼“絕對”了。Java堆是Java虛擬機器所管理的記憶體中最大的一塊,是被所有執行緒共享的一塊記憶體區域。

說到這裡可能會有一個疑問,方法中也有物件,那麼物件到底分配在堆上還是棧上呢。首先棧中存放的是基本資料型別和物件引用,而不是真正的物件,物件引用指向的記憶體幾乎都在堆上(為什麼說幾乎呢,因為一些常量分配在方法區,所以物件引用也可能指向方法區,但是這裡只需要知道棧和堆到底存放的是什麼就可以了)。

Java堆可以處於物理上不連續的記憶體空間中,只要邏輯上是連續的就可以了。

在Java虛擬機器規範中,這個區域有一個異常情況:當堆中沒有記憶體完成例項分配,並且堆也無法再擴充套件時,將會丟擲OutOfMemoryError異常。

方法區(執行緒公有

方法區用於儲存已被虛擬機器載入的類資訊、常量、靜態變數、即時編譯器編譯後的程式碼等資料。Java虛擬機器規範把方法區描述為堆的一個邏輯部分,但是平常我們在分析的時候還是習慣分開去說,方法區還有一個別名叫非堆。

很多開發者也把方法區稱之為“永久代”,本質上兩者並不是等價的,只不過HotSpot虛擬機器選擇把GC分代手機擴充套件至方法區,或者是永久代來實現方法區,才產生了這種稱呼。

Java虛擬機器規範對方法區的限制十分寬鬆,除了和Java堆一樣不需要連續的實體記憶體和可以選擇固定大小或者可擴充套件之外,還可以選擇不進行垃圾收集,因為這個區域的垃圾收集條件比較苛刻,但是並不代表這個區域不需要垃圾回收,Sun公司的bug裡表中,就曾出現過若干個嚴重的bug是由於低版本的HotSpot虛擬機器對此區域未完全回收而導致的記憶體洩漏。

這裡我們重點講一個方法區中的重要概念——執行時常量池。執行時常量池是方法區的一部分,Java檔案編譯形成的Class檔案中除了有類的版本、欄位、方法、介面等資訊之外,還有一項資訊是常量池,用於儲存編譯期生成的各種字面量和符號引用,這部分內容將在類載入後進入方法區的執行時常量池中存放。

執行時常量池具有動態性,並不一定是編譯期產生的常量才能放入常量池,執行期間也可以將新的常量放入常量池中,比如String類的intern()方法。

在Java虛擬機器規範中 ,這個區域有一個異常情況:當方法區中無法滿足記憶體分配需要時,將丟擲OutOfMemoryError異常。

以上就是Java虛擬機器管理的5塊執行時資料區,我們還有一塊記憶體區域要介紹,這一塊雖然不在虛擬機器管理範圍內,但是也很重要。

直接記憶體

在JDK1.4中新加入了NIO(New Input/Output)類,引入了一種基於通道與緩衝區的I/O方式,可以使用Native函式庫直接分配堆外記憶體,然後通過一個儲存在Java堆中的DirectByteBuffer物件作為這塊記憶體區域的引用進行操作。這樣能夠在一些情況下顯著提高效能,避免在Java堆和Native堆中來回複製資料。

這塊記憶體不會受到Java堆的大小限制,但是還是受到本機總記憶體限制,所以也有可能導致OutOfMemoryError異常。

到這裡Java的記憶體區域基本上就介紹完成了。

如有錯誤,歡迎指摘