1. 程式人生 > >Java的記憶體管理機制之記憶體區域劃分

Java的記憶體管理機制之記憶體區域劃分

      各位,好久不見。先做個預告,由於最近主要在做Java服務端開發,最近一段時間會更新Java服務端開發相關的一些知識,包括但不限於一些讀書筆記、框架的學習筆記、和最近一段時間的思考和沉澱。先從Java虛擬機器的記憶體開始吧。

Java虛擬機器在執行Java程式的過程中會把它所管理的記憶體劃分為若干個不同的資料區域。根據《Java虛擬機器規範(第2版)》規定,Java虛擬機器所管理的記憶體包括以下幾個執行區域。如下圖所示:

   

這些區域有各自的用途,各自的建立和銷燬時間(各自的生命週期)。

 

1. 程式記數器

 (執行緒私有,記憶體區域唯一在Java虛擬機器規範中沒有規定任何OutOfMemoryError情況的區域)

它可以看做是當前執行緒所執行的位元組碼的行號指示器。在虛擬機器概念模型裡(僅是概念模型),位元組碼直譯器工作時就是通過改變這個計數器的值來選取下一條需要執行的位元組碼指令,分支、迴圈、跳轉、異常處理、執行緒恢復等基礎功能都需要依賴這個計數器來完成。

由於Java虛擬機器的多執行緒是通過執行緒輪流切換分配處理器執行時間的方式來實現的,在任何一個確定的時刻,一個處理器(對於多核處理器來說是一個核心)只會執行一條執行緒中的指令。因此為了執行緒切換後能恢復到正確的位置,每條執行緒都需要有一個獨立的程式技術器,各條執行緒之間的計數器互不影響,獨立儲存,這類記憶體區域為“執行緒私有”的記憶體。

如果執行緒正在執行的是一個Java方法,程式計數器記錄的是正在執行的虛擬機器位元組碼指令的地址;如果是正在執行的是Nativie方法,則記錄的值為空(Undefined)。

 

2.Java虛擬機器棧(執行緒私有,生命週期與執行緒相同)

虛擬機器棧描述的是Java方法執行的記憶體模型:每個方法被執行的時候都會同時建立一個棧幀(Stack Frame)用於儲存區域性變量表、操作棧、動態連結、方法出口等資訊。每一個方法被呼叫直至執行完成的過程,就對應著一個棧幀在虛擬機器中從入棧到出棧的過程。

區域性變量表存放的是啥?它存放了編譯期可知的各種基本資料型別 (boolean、byte、char、short、int、long、double)、物件引用

(reference型別,根據不同的虛構機實現,它可能是一個指向物件起始地址的引用指標,也可能是代表物件的控制代碼或者其他與此物件相關的位置)和returnAddress型別(提向了一條位元組碼指令的地址)

該區域會產生的兩種異常情況

(1)StackOverflowError異常:請求的棧深度大於虛擬機器所允許的深度

(2)OutOfMemoryError 異常:當擴充套件時無法申請到足夠的記憶體

 

3.本地方法棧(執行緒私有)

本地方法棧與虛擬機器棧作用相似,區別是虛擬機器執行Java方法(位元組碼)服務,而本地方法棧則是為虛擬機器使用到的Native方法服務。

該區域和虛擬機器棧一樣也會產生的StackOverflowError異常、OutOfMemoryError 異常

 

4.Java堆(執行緒共享,記憶體中最大的一塊)

在虛擬機器啟動時建立,唯一目的就是存放物件例項,Java堆可以處於物理上不連續的記憶體空間中,只要邏輯上連續的即可。可以實現成固定的,也可以是擴充套件的,當前主流的虛構機都是按照可擴充套件來實現的(通過-Xmx 和-Xms 控制),如果堆中沒有記憶體完成例項分配,並且堆也無法擴充套件,將會丟擲OutOfMemoryError異常。

垃圾收集器的主要區域,所以也被稱為”GC堆“。

從記憶體回收的角度看,分為了新生代和老年代;再細緻一點的有Eden空間、From Survivor空間、To Survivor空間等。

從記憶體分配的角度看,執行緒共享的Java堆中可能劃分出多個執行緒私有的分配緩衝區(Thread Local  Allocation Buffer,TLAB)。

 

5.方法區(別名:Non-Heap (非堆),執行緒共享)

它用於儲存已被虛擬機器載入的類資訊常量、靜態變數、即時編譯後的程式碼等資料。

方法區和永久代:本質上兩者並不等價,僅僅是因為HotSpot虛擬機器的設計團隊選擇把GC分代收集擴充套件至方法區,或者說使用永久代來實現方法區而已。對於其他虛擬機器(如BEA Jrockit、IBM J9等)來說是不存在永久代的概念的。即使HotSpot虛擬機器本身,根據官方釋出的路線圖資訊,也有放棄永久代的並”搬家“至Native Memory來實現方法區的規劃了(Java 1.8 已放棄永久代,替代者元空間(Metaspace):與Oracle JRockit 和 IBM JVM類似,JDK 8.HotSpot JVM開始使用本地化的記憶體存放類的元資料,這個空間叫做元空間(Metaspace)

方法區可以選擇不實現垃圾收集,這個區域的記憶體回收主要是針對常量池的回收和對型別的解除安裝。

當方法區無法滿足記憶體分配需求時,將會丟擲OutOfMemoryError異常。

 執行時常量池,是方法區的一部分。Class檔案中除了有類的版本、欄位方法、介面等描述資訊外,還有一項資訊是常量池,用於存放編譯器生成的各種字面量和符號引用,這部分內容將在類載入後存放到方法區的執行時常量池中。

直接記憶體

直接記憶體(Direct Memory)並不是虛擬機器執行時的一部分,也不是Java虛擬機器規範中定義的記憶體區域,但是這部分也被頻繁的使用,而且也可能導致OutOfMemoryError異常出現。那什麼是直接記憶體?

在JDK1.4中新加入了NIO(New Input/Output)類,引入了一種基於通道(Channnel)與快取區(Buffer ) 的I/O方式,它可以使用Native函式直接分配堆外記憶體,然後通過一個儲存在Java堆裡的DirectByteBuffer物件作為這塊記憶體的引用進行操作。目的是在一些場景中提高效能,因為避免了Java堆和Native堆中來回複製資料。本機直接記憶體不會受到Java堆大小的限制,但會受到本機總記憶體的限制。這個地方是需要配置虛擬機器引數一併考慮進去的,動態擴充套件時可能會出現OutofMemoryError異常。

 

參考:《深入理解Java虛擬機器:JVM高階特性與最佳實踐》

本文首發於個人微信公眾號:webguan ;歡迎您的關注