1. 程式人生 > >【java虛擬機器】java記憶體區域與記憶體溢位異常

【java虛擬機器】java記憶體區域與記憶體溢位異常

本文參考自《深入理解Java虛擬機器》一書。主要總結一下java虛擬機器記憶體的各個區域,以及這些區域的作用、服務物件以及其中可能產生的問題。

##1. 執行時資料區域
  java虛擬機器在執行java程式的過程中會把它說管理的記憶體劃分為若干個不同的資料區域,這些區域都有各自的用途,以及建立和銷燬的時間,有的區域隨著虛擬機器程序的啟動而存在,有些區域則依賴使用者執行緒的啟動和結束而建立和銷燬。具體如下圖所示:
執行時資料區域
  結合這張圖,下面逐個來分析一下每個資料區域的特點。
###1.1 程式計數器(Program Counter Register)
  程式計數器是一塊較小的記憶體空間,可以看作是當前執行緒所執行的位元組碼的行號指示器

。啥意思呢?我們知道,CPU的計算時間是以分片的方式給到每個執行緒的(換句話說,所謂的並行其實本質上還是序列),比如執行緒A執行到了一個地方,CPU將控制權給了執行緒B,那麼執行緒A重新獲得CPU的資源時,如何恢復到剛才執行的地方呢?這就是程式計數器要乾的事了!它能幫助執行緒A找到剛剛執行的地方,從而繼續剛剛的執行。
  為了執行緒切換後能恢復到正確的執行位置,每個執行緒都需要有一個獨立的程式計數器,各條執行緒之間的計數器互不影響,獨立儲存。所以程式計數器是執行緒私有的。
  另外,程式計數器是唯一一個在java虛擬機器規範中沒有規定任何OutOfMemoryError情況的區域。

###1.2 Java虛擬機器棧


  Java虛擬機器棧所佔有的記憶體空間也就是我們平時所說的“棧記憶體”,並且也是執行緒私有的,生命週期與執行緒相同。虛擬機器棧描述的是Java方法執行的記憶體模型:每個方法在執行的同時,都會建立一個棧幀,用於儲存區域性變量表(基本資料型別,物件的引用和returnAddress型別)、運算元棧、動態連結、方法出口等資訊
  區域性變量表所需的記憶體空間在編譯期間完成分配,當進入一個方法時,這個方法需要在棧幀中分配多大的區域性變數空間是完全確定的,在方法執行期間不會改變區域性變量表的大小。
  每一個方法被呼叫直至執行完成的過程,就對應著一個棧幀在虛擬機器棧中從入棧到出棧的過程。對於Java虛擬機器棧,有兩種異常情況:

1)如果執行緒請求的棧深度大於虛擬機器所允許的深度,將丟擲StackOverflowError異常;
2)如果虛擬機器棧在動態擴充套件時,無法申請到足夠的記憶體,就會丟擲OutOfMemoryError;

###1.3 本地方法棧
  本地方法棧和虛擬機器棧所發揮的作用非常相似,它們之間的區別主要是,虛擬機器棧是為虛擬機器執行Java方法(也就是位元組碼)服務的,而本地方法棧則為虛擬機器使用到的Native方法服務
  與虛擬機器棧類似,本地方法棧也會丟擲StackOverflowError和OutOfMemoryError異常。
###1.4 Java堆
  Java堆是Java虛擬機器所管理的記憶體中最大的一塊。Java堆在主記憶體中,是被所有執行緒共享的一塊記憶體區域,其隨著JVM的建立而建立,堆記憶體的唯一目的是存放物件例項和陣列。同時Java堆也是GC管理的主要區域
  Java堆物理上不需要連續的記憶體,只要邏輯上連續即可。如果堆中沒有記憶體完成例項分配,並且也無法再擴充套件時,將會丟擲OutOfMemoryError異常。

###1.5 方法區
  方法去是所有執行緒共享的一個記憶體區域。用於儲存已被虛擬機器載入的類資訊、常量、靜態變數、即時編譯器編譯後的程式碼等資料。方法區也有一個別名叫做Non-Heap(非堆),用於與Java堆區分。對於HotSpot虛擬機器來說,方法區又習慣稱為“永久代”(Permancent Generation),但這只是對於HotSpot虛擬機器來說的,其他虛擬機器的實現上並沒有這個概念。相對而言,垃圾收集行為在這個區域比較少出現,但也並非不會來收集,這個區域的記憶體回收目標主要是針對常量池的回收和對型別的解除安裝上
  根據Java 虛擬機器規範的規定,當方法區無法滿足記憶體分配需求時,將丟擲OutOfMemoryError 異常。
###1.6 執行時常量池
  執行時常量池屬於方法區。Class檔案中除了有類的版本、欄位、方法、介面等描述資訊外,還有一項資訊是常量表,用於存放編譯期生成的各種字面常量和符號引用,這部分內容將在類載入後進入方法區的執行時常量池中存放(JDK1.7開始,常量池已經被移到了堆記憶體中了)。也就是說,這部分內容,在編譯時只是放入到了常量池資訊中,到了載入時,才會放到執行時常量池中去。執行時常量池縣歸於Class檔案常量池的另外一個重要特徵是具備動態性,Java語言並不要求常量一定只有編譯期才能產生,也就是並非預置入Class檔案中常量池的內容才能進入方法區的執行時常量池,執行期間也可能將新的常量放入池中,這種特性被開發人員利用的比較多的是String類的intern()方法。
  當方法區無法滿足記憶體分配需求時,將丟擲OutOfMemoryError異常,常量池屬於方法區,同樣可能丟擲OutOfMemoryError異常。
  下面針對記憶體區域模型做一個小結:
##2. java記憶體區域模型總結

記憶體區域 執行緒私有 主要作用 溢位異常
程式計數器 記錄當前執行緒執行的位置 無異常
虛擬機器棧 儲存區域性變量表(基本資料型別,物件的引用和returnAddress型別)、運算元棧、動態連結、方法出口等資訊(java方法) StackOverflowError和OutOfMemoryError
本地方法棧 和虛擬機器棧相似,區別本地方法棧為虛擬機器使用到的Native方法服務 StackOverflowError和OutOfMemoryError
存放物件例項和陣列 OutOfMemoryError
方法區 用於儲存已被虛擬機器載入的類資訊、常量、靜態變數、即時編譯器編譯後的程式碼等資料 OutOfMemoryError