Java內存區域的劃分和異常
Java內存區域的劃分和異常
運行時數據區域
JVM在運行Java程序時候會將內存劃分為若幹個不同的數據區域。
程序計數器
線程私有。可看作是當前線程所執行的字節碼的行號指示器,字節碼解釋器的工作是通過改變這個計數值來讀取下一條要執行的字節碼指令。
多線程是通過線程輪流切換並分配處理器執行時間來實現的,任何一個時刻,一個內核只能執行一條線程中的指令。為了線程切換後能恢復到正確的執行位置,每條線程都需要一個獨立的程序計數器。這就是一開始說的“線程私有”。如果線程正在執行的方法是Java方法,計數器記錄的是虛擬機字節碼的指令地址;如果是Native方法,計數器值為空。程序計數器是唯一一個在Java虛擬機規範中沒有規定OOM(OutOfMemoryError)情況的區域
Java虛擬機棧
線程私有,生命周期和線程相同。Java虛擬機棧描述的是Java方法的內存模型:每個方法在執行時都會創建一個棧幀,存儲局部變量表、操作數棧、動態鏈接、方法出口信息,每一個方法從調用到結束,就對應這一個棧幀在虛擬機棧中的進棧和出棧過程。局部變量表保存了各種基本數據類型(int、double、char、byte等
)、對象引用(不是對象本身)和returnAddress類型(指向了一條字節碼地址)。
這部分區域可能發生兩種異常:
- 線程請求的棧深度大於虛擬機所允許的深度,拋出StackOverflowError;
- 虛擬機棧擴展時無法申請到足夠的內存,拋出OutOfMemoryError。
本地方法棧
上述虛擬機棧為JVM執行Java方法服務,本地方法則為執行Native服務。其他和虛擬機棧類似,也會拋出StackOverflowError、OutOfMemoryError。
Java堆
常說的“棧內存”、“堆內存”,其中前者指的是虛擬機棧,後者說的就是Java堆了。Java堆是被線程共享的。在虛擬機啟動時被創建。
Java堆的作用是存放對象實例,Java堆可以處於物理上不連續的內存空間中,只要求邏輯上連續即可。
方法區
線程共享的區域。存儲已被虛擬機加載的類信息、常量、靜態變量、即使編譯器編譯後的代碼等數據。方法區無法滿足內存分配需求時,拋出OutOfMemoryError。
運行時常量池
運行時常量池是方法區的一部分。C用於存放編譯期生成的各種字面常量和符號引用,將在類加載後進入方法區的運行時常量池中存放。Java語言不要求常量只能在編譯期產生,換言之,在運行期間也能將新的常量放入。
直接內存
直接內存不屬於虛擬機運行時數據區的一部分,也不是內存區域。本機直接內存的分配不會受到Java堆的大小限制,但終究是內存,如果各個內存區域總和大於物理內存限制,還是會出現OutOfMemoryError。
對象的創建過程
虛擬機遇到一條"new"指令:
- 首先檢查這個指令的參數是否能在常量池中定位到一個類的符號引用;
- 檢查這個符號引用代表的類是否已被加載、解析、初始化;(如果沒有,則必須先進行類的加載)
- 在Java堆中為新對象分配內存,所需大小在類加載後就確定了;
- 將分配到的內存空間都初始化為0(不包括對象頭)
- 類的初始化,即init方法,吧對象按照程序員的意願初始化為想要的值。
對象的內存布局
對象在內存中存儲的布局可以分為3塊區域:
- 對象頭
- 實例數據
- 對齊補充
對象頭:存儲對象自身的運行時數據,比如哈希碼、GC分代年齡、鎖狀態標誌、線程持有的鎖、偏向線程ID等。另外還有一部分是類型指針,即對象指向它的類元數據的指針,虛擬機通過該指針來確定這個對象屬於哪個類的實例。
實例數據:對象真正有效的信息,在程序中定義的各種類型的字段內容;
對齊補充:非必須,占用符的作用。
對象的訪問定位
Java程序通過棧上的引用來操作堆上的實例對象。比如
Person p = new Person();
這裏p就是引用,new出來的Person對象是實例。
這個引用沒有規定要如何定位、訪問堆中的對象具體位置。主流的有兩中訪問方式:
- 句柄。Java堆中會劃分出一塊內存作為句柄池,引用存儲了對象的句柄地址,而句柄中包含了對象實例數據和類型數據。好處是,對象被移動時,只需改變句柄中的地址,引用本身無需修改。
- 直接指針。引用中存儲的直接就是對象地址。好處是速度更快,由於引用直接表示實例對象的地址,節省了一次指針定位操作。Sun HotSpot使用的正是這種方式。
by @sunhaiyu
2018.5.28
Java內存區域的劃分和異常