《深入理解Java虛擬機器》個人讀書總結——JAVA虛擬機器記憶體

GrayHJX發表於2017-05-06

《深入理解Java虛擬機器》個人讀書總結——JAVA虛擬機器記憶體

最近在讀《深入理解Java虛擬機器》,網上對Java虛擬機器的總結有很多,自己覺得自己也應該記錄一點個人的讀書總結,以便日後複習方便。
隨著開發工作的逐漸深入,對Java的理解不能止步於crud,Java不像C語言,Javaer是不需要自己控制記憶體的,一旦出現常見的OutOfMemoryError或StackOverflowError,如果不瞭解虛擬機器是怎樣使用記憶體的,那麼排查錯誤將會出現一定的阻礙。

執行時資料區域

Java虛擬機器在執行Java程式的過程中會把它所管理的記憶體劃分為若干個不同的資料區域。具體如圖所示:
這裡寫圖片描述
由圖可以看出,JVM將記憶體主要劃分為:方法區、虛擬機器棧、本地方法棧、堆、程式計數器五大塊。

程式計數器

程式計數器是一塊較小的記憶體空間,在大學作業系統一課中我們知道程式計數器是用於存放下一條指令所在單元的地址的地方。為了保證程式(在作業系統中理解為程式)能夠連續地執行下去,處理器必須具有某些手段來確定下一條指令的地址。而程式計數器正是起到這種作用,所以通常又稱為指令計數器。在虛擬機器的概念模型中,位元組碼直譯器工作時也是通過改變這個計數器的值來選取下一條需要執行的位元組碼指令。
Java虛擬機器的多執行緒是通過執行緒輪流切換並分配處理器執行時間片來實現,在任何一個確定的時刻,一個處理器都只會執行一條執行緒腫的指令,因此,為了確保執行緒切換之後能恢復到正確的執行位置,每條執行緒都需要一個獨立的程式計數器,這樣就互不影響獨立儲存,因此程式計數器是執行緒私有的記憶體。
此記憶體區域是唯一一個在Java虛擬機器規範中沒有規定任何OutOfMemoryError情況的區域。

虛擬機器棧

虛擬機器棧也是執行緒私有的,它的生命週期與執行緒相同。那個棧可以理解成我們平時所熟悉的資料結構的那個棧,裡面的一個個棧元素我們叫它棧幀(其實是一種資料結構,後面會說)。每個方法在執行的同時會建立一個棧幀,用於儲存區域性變數表、運算元棧,動態連結、方法出口等資訊。然後每個方法從呼叫到執行結束就相當於這些棧幀在虛擬機器棧中出棧入棧的過程。
在Java虛擬機器規範中,對這個區域規定了兩種異常狀況:如果執行緒請求的棧深度大於虛擬機器所允許的深度,將丟擲StackOverflowError異常;如果虛擬機器棧可以動態擴充套件(通過虛擬機器引數-Xss控制),如果擴充套件到無法申請到足夠的記憶體就會丟擲OutOfMemoryError異常。

本地方法棧

這個棧和上面所說的棧其實是作用相似的,虛擬機器棧是為Java方法服務,這個本地方法棧則是為native方法服務。虛擬機器規範丟擲的異常也是和虛擬機器規範一樣的,正因為虛擬機器規範中沒怎麼硬性規定,sun的HotSpot虛擬機器直接就將虛擬機器棧和本地方法棧合二為一了。

堆可以說是虛擬機器中所管理的記憶體中最大的一塊了。這塊記憶體是被所有執行緒都共享的,主要的作用是用來存放物件例項的。所有的物件例項和陣列都要在堆上分配。當然,隨著JIT編譯器的發展現在也不是那麼”絕對”了。
Java堆是垃圾收集器管理的主要區域,由於現在的收集器基本上採用的都是分代收集演算法,所有Java堆可以細分為:新生代和老年代。在細緻分就是把新生代分為:Eden空間、From Survivor空間、To Survivor空間。在HotSpot中還提出了一個永久代的概念,從記憶體中摳出一部分用於儲存類的元資訊,類變數等內容,將其當成方法區來用,不過在Java8之後,這個概念被去掉了,換湯不換藥,現在改叫元空間。
Java虛擬機器規範規定,堆可以處於物理上不連續的記憶體空間中,只要邏輯上是連續的即可。在實現上即可以是固定大小的,也可以是可動態擴充套件的(通過虛擬機器引數 -Xmx和-Xms控制)。如果在堆中沒有記憶體完成例項分配,並且堆大小也無法在擴充套件時,將會丟擲OutOfMemoryError異常。

方法區

方法區和堆一樣,同樣是執行緒共享的記憶體區域,它用於儲存已被虛擬機器載入的類資訊、常量、靜態變數、即時編譯器編譯後的程式碼等資料,可以通過虛擬機器的引數-XXpermSize和-XX:MaxPermSize來限制方法區大小。

執行時常量池

常量池用於存放編譯期間生成的各種字面量和符號引用。這是一個class檔案中的一個區域。
Java程式並不一定要求常量在編譯期間產生,有可能在執行的時候會產生一個新的常量。為了實現這種動態性,在方法區中還有一個區域叫執行時常量池。在執行期間這裡的內容是可以修改的。同時,在類的載入後會把常量池的東西存放到執行時常量池。
這裡還要說的一個是字串常量池,我想說的是這個和執行時常量池並沒有什麼關係。字串常量池是執行緒共享的,在Java7之前是存在於方法區裡的,Java7之後被轉移到堆上了。現在要我說這兩個常量池是平級。
執行時方法區和方法區一樣,記憶體分配不足時會丟擲OutOfMemoryError異常。

直接記憶體

直接記憶體不是虛擬機器執行是資料區的一部分。也不是Java虛擬機器規範中定義的記憶體區域。但是這部分記憶體也被頻繁地使用,也有可能會導致OutOfMemoryError異常。
在JDK1.4中新加入了NIO,它可以使用Native函式庫直接分配堆外記憶體,這記憶體是不受虛擬機器控制的,但會受到本機的限制。所以在設定虛擬機器的引數-Xmx等引數時要考慮到各記憶體區域總和要小於實體記憶體限制才行,從而避免動態擴充套件的時候出現OutOfMemoryError異常。

相關文章