標籤:jvm java 記憶體 記憶體配置
Java虛擬機器運行時資料區
運行時資料區主要包括:方法區、堆、虛擬機器棧、本地方法棧、程式計數器。
其中方法區和棧是線程共用的地區,另外三塊地區是每個線程私人的地區。各個資料區的功能簡單說明如下:
程式計數器:當前線程所執行的位元組碼的行號指標。
虛擬機器棧:描述Java方法執行的記憶體模型——每個方法在執行的同時都會建立一個棧幀用於儲存局部變數表、運算元棧、動態連結、方法出口等資訊。每一個方法從調用直至執行完成的過程,就對應一個棧幀在虛擬機器棧中入棧到出棧的過程。如果棧的深度大於虛擬機器所允許的深度,將拋出StackOverflowError異常。如果棧擴充時無法申請到足夠的記憶體,將拋出OutOfMemoryError異常(下述的本地方法棧類同)。
本地方法棧:與虛擬機器棧的作用類似。虛擬機器棧為Java方法服務,本地方法棧為Native方法服務。
Java堆:在虛擬機器啟動的時候建立,作用是存放對象執行個體。通過-Xmx和-Xms控制堆的大小。如果在堆中沒有記憶體完成執行個體分配,並且堆也無法再擴充時,將拋出OutOfMemoryError異常。
方法區:作用是儲存已被虛擬機器載入的類資訊、常量、靜態變數等(在HotSpot中稱此地區為Permanent Generation)。
對象的建立
對象的建立包括四個步驟:類載入檢查、為新生對象分配記憶體、初始化為零值、設定對象頭。
一、類載入檢查
虛擬機器遇到一條new指令時,收件將檢查該指令的參數是否能在常量池(運行時常量池是方法區的一部分。Class檔案除了有類的版本、欄位、方法、介面等描述資訊外,還有一項資訊是常量池,用於存放編譯期產生的各種字面量和符號引用,這部分內容將在類載入後進入方法區的運行時常量池中存放。)中定位到一個類的符號引用,並且檢查這個符號引用代表的類是否已被載入、解析和初始化過。如果沒有,那必須先執行相應的類載入過程。
二、為新生對象分配記憶體
對象所需記憶體大小在類載入完成後便完全確定,則分配記憶體等同於把一塊確定大小的記憶體從Java堆中劃分開來。分兩種情況:Java堆中記憶體是絕對規整的(稱為指標碰撞,Bump the pointer)、記憶體不規整(稱為空白閑列表,Free List)。在規整的Java堆中,用過的記憶體放一邊,閒置記憶體放一邊,中間放一個指標作為分界點。在不規整的Java堆中,虛擬機器維護一個列表記錄哪些塊是可用的。選擇那種分配方式由Java堆是否規整決定,而Java堆是否規整由採用的垃圾收集器是否帶有壓縮整理功能決定。如:在使用Serial、ParNew等帶Compact過程的收集器時,採用的分配演算法是指標碰撞;而使用CMS基於Mark-Sweep演算法的收集器時,採用空閑列表。
三、分配記憶體過程需要保證安全執行緒
對分配記憶體空間的動作進行同步處理;或者是把記憶體配置的動作按照線程劃分在不同的空間進行(即本地線程分配緩衝Thread Local Allocation Buffer)。
四、對象頭的設定
說明是哪個類的執行個體、如何找到類的中繼資料資訊、對象的雜湊碼、對象的GC年齡。
對象的訪問定位
Java程式通過棧上得reference資料操作堆上得具體對象。有兩種訪問方式:使用控制代碼和直接指標。
一、使用控制代碼
reference中儲存的是穩定的控制代碼地址,在對象執行個體資料被移動時(記憶體回收時)只會改變控制代碼中的執行個體資料指標,而reference本身不需要改變。
二、使用直接指標
速度快,因為它節省了一次指標定位的時間開銷。
說明:本文的內容參考書籍《深入理解Java虛擬機器(第2版)》
Java記憶體地區——JVM系列<一>