Java虛擬機學習之運行時數據區域

Java虛擬機學習之運行時數據區域

內容選擇《深入理解java虛擬機》周志明著一書,尊重並感謝原著作者。

運行時數據區域

java虛擬機在運行java程序時,會把其所管理的內存分成若干個數據區域,它們都擁有各自的創建時間、銷燬時間以及用途。分爲如下兩種情況:

  • 隨着虛擬機進程的啓動而存在;
  • 依賴用戶線程的啓動和結束而建立以及銷燬;

根據《Java虛擬機規範(JavaSE 7版)》的規定,java虛擬機所管理的內存所包含的數據區域如下圖所示
java虛擬機運行時數據區

程序計數器

程序計數器是一塊比較小的內存空間(在計算java虛擬機內存大小可忽略該內存空間的大小),也可以理解爲它是當前線程所執行的行號指示器。字節碼解釋器工作時就是通過改變這個計數器的值來選取下一條需要執行的字節碼指令,需要依賴計數器完成的基礎功能:循環、跳轉、分支、異常處理、線程恢復等。
在java虛擬機的多線程中,爲了線程切換後能恢復到正確的執行位置,每條線程都需要有一個獨立的程序計數器,並且各條線程之間計數器互不影響,獨立存儲,這樣的內存區域被稱爲“線程私有”的內存。

  • 線程執行ava方法時,計數器記錄的是正在執行的虛擬機字節碼指令的地址;
  • 執行Native(本地)方法時,計數器的值則爲空(即未定義),該內存區域是唯一一個在java虛擬機規範中未規定任何OutOfMemoryError情況的區域。

Java虛擬機棧

java虛擬機棧也是線程私有的,並且它的生命週期和線程的生命週期相同。它描述的是java方法執行的內存模型:每個方法執行的同時會創建一個用於存儲局部變量表、動態鏈接、操作數棧、方法出口等信息的棧幀。每個方法從開始調用到執行完成的過程與棧幀在虛擬機棧中從入棧到出棧的過程相對應。
在java虛擬機規範中,對java虛擬機棧這塊數據區域規定了兩種異常狀況:
- 如果線程請求的棧深度大於虛擬機所允許的深度,那麼將拋出StackOverflowError異常;
- 如果虛擬機可以動態擴展(目前大部分java虛擬機都可動態擴展),但擴展時無法申請到足夠的內存空間,便會拋出OutOfMemoryError異常。

本地方法棧

本地方法棧所發揮的作用與前面講到的虛擬機棧非常相似,二者的區別是:

  • java虛擬機棧爲虛擬機執行java方法(也就是字節碼)服務;
  • 本地方法棧爲虛擬機使用的Native服務;

由於虛擬機規範中並沒有強制規定本地方法棧使用方法、語言以及數據結構,因而可以由具體的虛擬機去自由去實現它,例如我們常用的Sun HotSpot虛擬機就將本地方法棧和虛擬機棧合二爲一。本地方法棧與虛擬機棧一樣,也會拋出StackOverflowError和OutOfMemoryError異常。

Java堆

Java堆是在虛擬機啓動時創建,並用於存放對象實例的。幾乎所有的對象實例以及數組都在java堆中分配內存(JIT編譯器的發展逐漸成熟,棧上分配、標量替換優化技術的出現使得不是所有的都在堆上分配),因而對於大多數應用來說,java堆是java虛擬機所管理的內存中最大的一塊內存區域。這塊區域(java堆)是被所有線程共享的。
Java堆是垃圾收集器管理的主要區域,因而有時Java堆被稱爲“GC堆”(Collected Heap)。爲了更好地回收內存(分配內存),java堆可採用如下兩種方法劃分:

  • 從內存回收角度來看,目前採用分代收集算法,可分爲:新生代與老年代。
  • 從內存分配角度來看,線程共享的java堆中可能劃分出多個線程私有的分配緩衝區(Thread Local Allcation Buffer)TLAB.

無論如何劃分,都與存放的內容無關,無論哪個區域,存儲的都仍然是對象實例以及數組。
根據java虛擬機規範,java堆可以處於物理上不連續的內存空間,只要實現邏輯上連續即可。如果在堆中沒有內存去完成實例的分配,並且堆也無法被擴展,那麼將會拋出OutOfMemoryError異常。

方法區

方法區與java堆一樣都是各個線程共享的內存區域。方法區是用於存放已被虛擬機加載的類信息、靜態變量、常量、即時編譯器編譯後的代碼等數據。在java虛擬機規範中,方法區被描述爲堆的一個邏輯部分,爲了使它和java堆區分開,因而方法區有個別名將Non-Heap(非堆)。
java虛擬機規範對方法區的限制非常寬鬆,除了和java堆一樣不需要連續的物理內存和可以選擇固定大小或者可擴展外,還可以選擇不實現垃圾收集。方法區的內存回收目標主要是針對常量池的回收和對類型的卸載。根據規定,當方法區無法滿足內存分配時,將拋出OutOfMemoryError異常。

運行時常量池

運行時常量池是方法區的一部分。Class文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項信息是常量池,它是用於存放編譯期生成的各種字面量、符號引用以及翻譯出來的直接引用。這部分內容將在類加載後進入方法區的運行時常量池中存放。
運行常量池不一定只有在編譯器才能產生,也可能在運行期間將新的常量放入常量池中。開發人員可以使用String類的intern()方法,將新的常量在運行期間放入常量池中。
既然是運行時常量池是方法區的一部分,自然受到方法區內存的限制,當常量池無法再申請到內存時會拋出OutOfMemoryError異常。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章