1. 程式人生 > >深入理解java虛擬機器(一)----jvm記憶體模型

深入理解java虛擬機器(一)----jvm記憶體模型

最近大致的學習了一下jvm的相關技術,發現深入理解java虛擬機器這本書很不錯,所以想將這本書的內容的重難點在blog總結一下,一是為了鞏固這些知識,二是為了把這些重點單獨寫出來,讓初學者在學習的時候有一個大致的框架以至於學起來不至於那麼迷茫

學習java虛擬機器,有兩個最重要的機制需要知道:
1. 自動記憶體管理機制
2. 虛擬機器執行子系統
下面將首先介紹jvm的自動記憶體管理機制:

java和c++之間有一堵由記憶體動態分配和垃圾回收機制所圍成的高牆。要了解具體是如何進行記憶體分配和回收之前需要了解jvm的執行時記憶體模型(資料區域),本文將詳細的介紹jvm的記憶體模型。
java虛擬機器在執行java程式的過程中會把它所管理的記憶體劃分為若干個不同的資料區域。這些區域各有各自的用途,以及建立和銷燬的時間。下圖便是一個jvm執行時的記憶體模型
jvm記憶體模型


下面分模組對這些區域做簡要的介紹:
1. 程式計數器:它可以看作是當前執行緒所執行的位元組碼(位元組碼 )的行號指示器,在虛擬的概念模型中 ,位元組碼直譯器工作時就是通過改變這個計數器的值來選取下一條需要執行的位元組碼指令,分支,迴圈,跳轉,異常處理,執行緒恢復等基礎功能。為了在多執行緒中,執行緒切換後能恢復到正確的執行位置,每條執行緒需要有一個獨立的程式計數器,即這個部分是執行緒私有的。
如果執行緒執行的是一個java方法,那麼計數器記錄的是虛擬機器位元組碼指令的地址;如果執行的是Native方法,則該計數器值為空(Undefined)。此記憶體區域是唯一一個不會發生OutOfMemoryError的區域。
2. java虛擬機器棧:與程式計數器一樣,java虛擬機器棧同樣是執行緒私有的,它的生命週期和執行緒相同。虛擬機器棧描述的是java方法執行的記憶體模型:每個方法在執行的同時會建立一個棧幀(Stack Frame)用於儲存區域性變量表,運算元棧,動態連結,方法出口等資訊。每一個方法從呼叫到執行完成的過程,就對應著一個棧幀在虛擬機器棧從入棧到出棧的過程。這部分內容比較重要,詳情見
jvm棧幀詳解
。區域性變量表所需的記憶體空間在編譯器件完成分配,在方法執行期間不會改變區域性變量表的大小。
在jvm規範中,對這個區域指定了兩種異常狀況:1.如果執行緒請求的棧深度大於虛擬機器所允許的深度(多為無限遞迴):丟擲StackOverflowError異常;如果虛擬機器棧可以動態擴充套件,如果擴充套件時無法申請到足夠的記憶體,就會丟擲OutOfMemoryError異常。
虛擬機器引數:HotSpot不區分本地方法棧和虛擬機器棧,所以-Xoss引數無效,棧容量只由-Xss引數決定
3. 本地方法棧:和虛擬機器棧差不多,區別在於虛擬機器棧為虛擬機器執行java方法(位元組碼)服務,而本地方法棧則為虛擬機器使用到的Native方法服務,與虛擬機器棧一樣,本地方法棧也會丟擲StackOverFlowError異常或者OutOfMemoryError異常。
4. java堆:Java堆是jvm所管理記憶體中最大的一塊。Java堆是被所有執行緒共享的一塊區域。在虛擬機器啟動時建立。java堆是GC的主要區域。從記憶體回收的角度來看,由於現在收集器基本採用分代回收演算法,所以java堆還可以細分為新生代和老年代;從記憶體分配的角度來看,執行緒共享的java堆可以劃分出多個執行緒私有的分配緩衝區。但無論如何劃分,都與存放內容無關,無論哪個區域,儲存的依然是物件的例項。進一步劃分只是為了更好的回收記憶體和更好的分配記憶體。

根據jvm規範的規定,java堆可以處於物理上不連續的記憶體空間中,只要邏輯上市連續的即可。如果堆中沒有記憶體完成實力分配並且堆也無法再擴充套件時,將會丟擲OutOfMemoryError異常。
虛擬機器引數:-Xms:堆的最小值;-Xmx:堆的最大值;將這兩個設為一樣可以防止堆自動擴充套件。-XX:+HeapDumpOnOutOfMemoryError 可以讓虛擬機器在出現記憶體溢位異常時Dump出當前的記憶體堆轉儲快照以便分析,一般可以用記憶體映像分析工具(如:Eclipse Memory Analyzer)分析轉儲快照,重點是確認記憶體中的物件是否是有必要的,即記憶體洩漏還是記憶體溢位。如果是記憶體洩漏:分析到GC Roots引用鏈,準確定位洩漏程式碼的位置;如果是記憶體溢位:檢查堆引數是否可根據實體記憶體在調大,是否有物件宣告週期過長等問題。
5. 方法區:與java堆一樣,是各個執行緒共享的記憶體區域,它用於儲存已被虛擬機器載入的類資訊 ,常量,靜態變數,即時編譯器編譯後的程式碼等資料。不同虛擬機器在這裡的實現不盡相同,這些區別可以在String.intern()方法看出來。這個區域很少進行垃圾回收,但並非”永久”,這個區域的記憶體回收目標主要是針對常量池的回收和對型別的解除安裝,當方法區無法滿足記憶體分配需求時,將丟擲OutOfMemoryError異常。
虛擬機器引數:在JDK 1.6 之前,可以通過-XX : PermSize和-XX : MaxPermSize限制方法區的大小從而限制常量池的容量
6. 執行時常量池:它其實是方法區的一部分,Class檔案中除了有類的版本,欄位,方法,介面等描述資訊外,還有一項資訊是常量池,用於存放編譯器生成的各種字面量和符號引用,這部分內容 將在類載入後進入方法區的執行時常量池中存放。一般來說,除了儲存Class檔案中的符號引用外,還會把翻譯出來的直接引用也儲存到執行時常量池中。並且執行時常量池具備動態性,所以不僅有class檔案中內容才能進入常量池中。執行時也可能將新的常量放入池中,比如String.intern()方法,jvm規範規定,當常量池無法再申請到記憶體時會丟擲OutOfMemoryError異常。
7. 直接記憶體:直接記憶體並不是虛擬機器執行時資料區的一部分,也不是java虛擬機器規範中定義的記憶體區域。比如NIO中,引入了一種基於通道和緩衝區的IO方式,它可以使用Native函式直接分配堆外記憶體,然後通過一個儲存在java堆中的DirectByteBuffer物件作為這塊記憶體的引用進行操作。這部分記憶體的分配不會受到java堆記憶體的限制,但是會受到本機總記憶體和處理器定址空間的限制,會導致OutOfMemoryError異常。
虛擬機器引數:DirectMemory容量可以通過-XX : MaxDirectMemorySize指定,如果不指定,則和java堆最大值(-Xmx)一樣

下面將以HotSpot虛擬機器在Java堆中物件分配,佈局和訪問的全過程為例來講解一下虛擬機器的細節實現:
jvm對new的反應(物件的建立):
1. 會去檢查這個指令的引數是否能在常量池中定位到一個類的符號引用,並且檢查這個符號引用代表的類是否被載入,解析和初始化過(虛擬機器執行子系統)。如果沒有,那必須先執行類載入過程
2. 在類載入檢查通過後,接下來虛擬機器將為新生物件分配記憶體。物件所需記憶體的大小在類載入完成後便可完全確定,為物件在堆中分配空間有兩種方式:1. 指標碰撞(堆中記憶體規整),2. 空閒列表(堆中記憶體不規整)。選擇哪種分配方式由java堆是否規整決定,而java堆是否規整由所採用的gc是否帶有壓縮整理的功能決定。記憶體分配在多執行緒時並不安全,要同步。
3. 接下來,虛擬機器要對物件進行必要的設定,例如物件是哪個類的例項,如何找到類的元資料資訊,物件的hashcode,物件的GC分代年齡等資訊,這些資訊存放在物件頭中。
4. 現在,在jvm的角度,一個物件已經產生,但從java程式的角度,還需要呼叫建構函式,這樣一個真正可用的物件才算完全產生出來。
到目前為止,物件已經被建立,那麼物件在記憶體中的儲存佈局是什麼樣的呢?物件的記憶體佈局主要分為3塊區域:物件頭,例項資料,對齊填充。下面分別介紹這三個部分。
1.物件頭:HotSpot虛擬機器的物件頭包含兩部分資訊,第一部分用於儲存物件自身的執行時資料,如hashcode,GC分代年齡,執行緒持有的鎖等資訊;另一部分就是型別指標,即物件指向它的類元資料的指標,虛擬機器通過這個指標來確定這個物件是哪個類的例項,如果物件是一個java陣列,那麼物件頭中應該還有一個記錄陣列長度的資料。因為jvm可以通過普通java物件的元資料資訊來確定物件的大小,但是無法通過陣列的元資料資訊確定陣列的大小。
2. 例項資料:是物件真正儲存的有效資訊,也是在程式程式碼中定義的各種型別的欄位內容。無論是從父類繼承還是子類定義,都需要記錄。一般父類的資訊會在子類之前
3. 對齊填充:僅僅是佔位符的作用,因為物件的大小要是8位元組的整數倍,不夠的地方要填充。
知道了物件的記憶體佈局,那如何對物件進行定位呢? java程式是通過棧上的reference資料來操作堆上的具體物件。jvm規範並未定義reference這個也能用應該通過何種方式去定位堆中的物件。所以物件的訪問方式取決於虛擬機器的具體實現:
1. 控制代碼:java堆中會劃分出一塊記憶體作為控制代碼池,reference中儲存的是物件的控制代碼地址,而控制代碼中包含了物件例項資料與型別資料各自的具體地址資訊
handle
2. 直接指標:reference中直接儲存物件的地址
DP
兩種方式各有優劣:控制代碼的好處是物件移動(GC很多)時只會改變控制代碼中的例項資料指標,而reference本身不用修改;但是直接指標的訪問速度更快,一次定位,控制代碼需要二次。HotSpot是採用直接指標的方式對物件定位的。