深入理解jvm虛擬機——Java內存區域與內存溢出異常

1. 運行時數據區

如圖:

深入理解jvm虛擬機——Java內存區域與內存溢出異常

1.1 程序計數器

程序計數器是是線程隔離的區域,每個線程都有一個獨立的程序計數器。

它是一塊較小的內存空間,字節碼解釋器通過改變計數器來選取下一條要執行的字節碼指令,分支、循環、跳轉、異常處理、線程恢復等基礎功能都需要依賴它完成。

1.2 Java虛擬機棧

Java虛擬機棧也是線程隔離的,生命週期和線程相同。

虛擬機棧是Java方法執行的內存模型,每個方法執行時都會創建一個棧幀(stack frame)用於存儲局部變量表、操作數棧、動態鏈接、方法出口等信息,每個方法從調用到執行完成的過程,就對應著一個棧幀在虛擬機棧中入棧到出棧的過程。

兩種異常情況:如果線程請求的棧深度大於虛擬機所允許的深度,將拋出StackOverflowError異常;虛擬機棧動態擴展時無法申請到足夠的內存,就會拋出OutOfMemoryError異常。

1.3 本地方法棧

本地方法棧和虛擬機棧作用相似。區別在於虛擬機棧為虛擬機執行Java方法服務,而本地方法棧為虛擬機使用到的Native方法服務。

1.4 Java堆

對於大部分應用程序來說,Java堆是Java虛擬機所管理的內存中最大的一塊。Java堆是被所有線程共享的一塊內存區域,在虛擬機啟動時創建。此內存區域的唯一目的就是存放對象實例,幾乎所有的對象實例都在這裡分配內存。

可以通過-Xmx 和 -Xms來控制堆的大小,在控制檯輸入java -X可以看到他們的含義,-Xmx是設置堆的最大值, -Xms是這是堆的初始化大小。隊中沒有完成內存分配並且再也無法擴展時,就會拋出OutOfMemoryError異常。

1.5 方法區

方法區也是各個線程共享的內存區域,用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據。

Java8以後把它放在native memory了。那麼什麼是native memory?Thanks for the memory, Linux

1.6 運行時常量池

運行時常量池是方法區的一部分。

它用於存放編譯期生成的各種字面量和符號引用,這部分內容將在類加載進入方法區後進入方法區的運行時常量池中存放。

注意常量不一定是編譯期才能產生,運行時也可以比如String類的intern()方法。無法申請到內存時會拋出OOM。

1.7 直接內存

jdk1.4加入的NIO類,可以使用Native函數庫直接分配堆外內存,然後通過一個存儲在Java堆中的DirectByteBuffer對象作為這塊內存的引用進行操作。

直接內存不會受到Java堆大小的限制,但是會收到本級總內存和處理器尋址空間的限制。

2 虛擬機對象探秘

2.1 對象的創建

虛擬機在遇到new指令後會先去常量池中檢查是否有這個類的符號引用,並檢查這個符號引用代表的類是否已被加載、解析和初始化,如果沒有,那必須先執行類的加載過程(後面會介紹)。

對象所需的內存大小在類加載完成後便可完全確定。

分配內存的方法有兩種,一種是”指針碰撞“,在堆內存絕對規整的情況下使用。一種是空閒列表,在堆內存不規整的情況下使用。而Java堆是否規整又由所採用的垃圾收集器是否帶有壓縮功能決定。

2.2 對象的內存佈局

對象在內存中存儲的佈局可以分為3塊區域:對象頭、實例數據和對其填充。

對象頭有兩部分,一部分用於存儲對象自身的運行時數據,如哈希嗎、GC分代年齡等等。這部分數據在32位和64位虛擬機中分別問32bit和64bit。另一部分是類型指針,即對象指向它元數據的指針,虛擬機通過這個指針來確定你那個這個對象是哪個類的實例。

接下來的實例數據部分是真正存儲的有效信息,父類和子類中定義的都需要記錄。

第三部分對象填充並不一定存在,僅僅起佔位的作用,因為HotSpot VM的自動內存呢管理系統要求對象的起始地址必須是8字節的整數倍。

2.3 對象的訪問定位

目前主流的訪問方式有兩種:使用句柄和直接執政。

深入理解jvm虛擬機——Java內存區域與內存溢出異常

深入理解jvm虛擬機——Java內存區域與內存溢出異常

使用句柄的好處是穩定的句柄地址,對象移動只用改變句柄中的實例數據指針,指針最大好處是快。HotSpot是第二種。

深入理解jvm虛擬機——Java內存區域與內存溢出異常


分享到:


相關文章: