1. 程式人生 > 程式設計 >深入理解Java虛擬機器器(自動記憶體管理機制1)

深入理解Java虛擬機器器(自動記憶體管理機制1)

這一篇文章我們來學習Java虛擬機器器執行時資料區域。

1.概述

       對於Java開發同學來講,在虛擬機器器自動記憶體管理機制的幫助下,我們不需要為每一個new操作去寫配對的delete/free程式碼,不容易出現記憶體洩漏和記憶體溢位問題,但正因如此,一旦出現記憶體溢位和洩漏的問題,如果不瞭解虛擬機器器是怎樣使用記憶體的,那麼排查錯誤將會成為一項異常艱難的工作。

2.執行時資料區域

       Java虛擬機器器在執行Java程式的過程中會把它所管理的記憶體劃分為若干個不同的資料區域,這些區域都有各自的用途,以及建立和校會的時間,有的區域隨著虛擬機器器程式的啟動而存在,有的區域則依賴使用者執行緒的啟動和結束而建立和銷燬,Java虛擬機器器所管理的記憶體將會包括以下幾個執行時資料區域,如圖:

    

2.1 程式計數器

       程式計數器是一塊較小的記憶體,它可以看做是當前執行緒所執行的位元組碼的行號指示器。位元組碼直譯器在工作時就是通過改變這個計數器的值來獲取下一條需要執行的位元組碼指令,分支、迴圈、跳轉等基礎功能都需要依賴這個計數器來完成。

       由於Java虛擬機器器的多執行緒是通過執行緒輪流切換並分配處理器執行時間的方式實現的,因此,為了執行緒切換後能恢復到正確的執行位置,每個執行緒都需要有個獨立的程式計數器,各個小城之間計數器互不影響,獨立儲存。

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

2.2 虛擬機器器棧

       與程式計數器一樣,Java虛擬機器器棧也是執行緒私有的,虛擬機器器棧描述的是Java方法執行的記憶體模型,每個方法在執行的同時建立一個棧幀用於儲存區域性變量表、運算元棧、動態連結、方法出口等資訊,每個方法從呼叫到執行完成的過程,就對應著一個棧幀在虛擬機器器棧中的入棧到出棧的過程。

       區域性變量表存放了編譯期可知的各種基本資料型別(boolean、byte、char、short、int、long、float、double)、物件引用(reference型別,它不等同於物件本身,可能是一個指向物件起始地址的引用指標,也可能是指向一個代表隊向的控制程式碼或者其他與此物件相關的位置)和returnAddress型別(指向了一條位元組碼指令的地址)。

       在Java虛擬機器器規範中描述了兩種異常情況:
  • 如果執行緒請求的棧深度大於虛擬機器器所允許的最大深度,則丟擲StackOverflowError異常。
  • 如果虛擬機器器在擴充套件棧時無法申請到足夠的記憶體空間,則丟擲StackOverflowError異常。

2.3 本地方法棧

       本地方法棧與虛擬機器器棧發揮的作用是非常相似的,它們之間的區別不過是虛擬機器器棧為虛擬機器器執行Java方法服務,本地方法棧為虛擬機器器使用到的Native方法服務,與虛擬機器器棧一樣,本地方法棧也會跑出StackOverflowError和OutOfMemoryError異常。

2.4 堆

       Java堆是被所有執行緒多共享的一塊記憶體區域,在虛擬機器器啟動時建立,此記憶體區域的唯一目的就是存放物件例項,幾乎所有的物件實力都在這裡分配記憶體。

       Java堆是垃圾收集器管理的主要區域,因此很多時候也被稱作“GC堆”,從記憶體回收的角度來看,由於現在垃圾收集器基本都採用分代回收演演算法,所以Java堆還可以細分為:新生代和老年代,再細緻一點的又Eden空間、From Survivor空間、To Survivor空間等。從記憶體分配的角度來說,Java堆可能劃分出多個執行緒私有的分配緩衝區。不過無論如何劃分,都與存放內容無關,無論哪個區域,儲存的都仍然是物件例項。進一步劃分的目的是為了更好的回收記憶體,或者更快的分配記憶體。

       Java堆的所使用的記憶體在物理上不需要連續,邏輯上連續即可。Java堆的容量可以是固定的,也可以動態的擴充套件(通過-Xms 堆的初始化記憶體 和 -Xmx 堆的最大記憶體 控制)。

       在Java虛擬機器器規範中描述了一種異常情況:

  • 如果在堆中沒有足夠的記憶體來完成例項分配,並且堆也無法進行擴充套件時,則會丟擲OutOfMemoryError異常。

2.5 方法區

       方法區與Java堆一樣,是各個執行緒共享的記憶體區域,它用於儲存已被虛擬機器器載入的類資訊、常量、靜態變數、即使編譯器編譯後的程式碼等資料。

        HotSpot虛擬機器器中方法區也常被稱為 “永久代”,本質上兩者並不等價。僅僅是因為HotSpot虛擬機器器設計團隊用永久代來實現方法區而已,這樣HotSpot虛擬機器器的垃圾收集器就可以像管理Java堆一樣管理這部分記憶體,能夠省去專門為方法區編寫記憶體管理程式碼的工作,但是這並不是一個好主意,因為這樣更容易遇到記憶體溢位問題(永久代又 -XX:MaxPermSize的上限)。 

       方法區是Java堆的邏輯組成部分,它除了和Java堆一樣在物理上不需要連續和可以選擇固定大小或可擴充套件外,還可以選擇不實現垃圾收集。相對而言,垃圾收集行為在這個區域是比較出現的,但並非資料進入方法區後就“永久存在”了。

       Java虛擬機器器規範中描述了一種異常情況:

  • 如果方法區的記憶體空間不滿足記憶體分配需求時,Java虛擬機器器會丟擲OutOfMemoryError異常。

2.6 執行時常量池

       執行時常量池(Runtime Constant Pool)是方法區的一部分。Class檔案除了有類的版本、介面、欄位和方法等描述資訊,還包含了常量池,它用來存放編譯時期生成的字面量和符號引用,這些內容會在類載入後存放在方法區的執行時常量池中。

       Java虛擬機器器規範中描述了一種異常情況:
  • 當建立類或介面時,如果構造執行時常量池所需的記憶體超過了方法區所能提供的最大值,Java虛擬機器器會丟擲OutOfMemoryError異常。

2.7 直接記憶體

       直接記憶體並不是虛擬機器器執行時資料區的一部分,也不是虛擬機器器規範中定義的記憶體區域,但是這部分記憶體也被頻繁地使用。而且也可能導致OutOfMemoryError異常出現。

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

       本機直接記憶體的分配不會收到Java堆的限制,但是,既然是記憶體就會受到本機總記憶體大小以及處理器定址空間的限制。

參考資料
《深入理解Java虛擬機器器 第三版》
《Java虛擬機器器規範(Java SE7版)》