1. 運行時資料區
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 對象的訪問定位
目前主流的訪問方式有兩種:使用控制代碼和直接執政。
使用控制代碼的好處是穩定的控制代碼地址,對象移動只用改變控制代碼中的執行個體資料指標,指標最大好處是快。HotSpot是第二種。