標籤:
運行時資料區域 Java虛擬機器在執行Java程式的過程中會把它所管理的記憶體劃分為若干個大小不同的資料域;
這些地區都有各自的用途,以及建立和銷毀時間,有的地區會隨著虛擬機器進程的啟動而存在,有些地區則依賴使用者線程的啟動和結束而建立和銷毀。
程式計數器:程式計數器是一塊較小的記憶體空間,可以看作當前線程所執行的位元組碼的訊號指標;由於Java虛擬機器的多線程是通過線程輪流轉換並分配處理執行時間的方式來實現的,在任何一個確定的時刻,一個處理器都只會執行一條線程中的指令。因此,為了線程切換後能恢複到正確的執行位置,每條線程都需要一個獨立的程式計數器。各條線程之間的程式計數器互不影響,隔離儲存區 (Isolated Storage),這類記憶體地區為“線程私人”的記憶體; Java虛擬機器棧:Java虛擬機器棧也是線程私人的,它的生命週期與線程相同。虛擬機器棧為虛擬機器執行Java方法服務虛擬機器棧描述的是Java方法執行的記憶體模型:每個方法在執行的同時都會建立一個棧幀用於儲存局部變數表、運算元棧、動態連結、方法出棧口等資訊。每一個方法從調用直至執行完成的過程,就對應著一個棧幀在虛擬機器棧中從進棧到出棧的過程。
局部變數表存放了編譯期可知的各種基礎資料型別 (Elementary Data Type)(boolean、byte、char、short、int、float、long、double)、對象引用(reference類型)和returnAddress類型;其中 64位長度的long 和double類型會佔用 2個局部變數空間,其餘資料類型只佔用 1個。局部變數表所需要的記憶體空間在編譯期間完成分配。
在Java虛擬機器規範中,對這個地區規定了2中異常情況:1、如果線程請求的棧深度大於虛擬機器所允許的深度,將會拋出StackOverflowError(棧溢出)異常;2、如果虛擬機器棧可以動態擴充,如果擴充時無法申請到足夠的記憶體,將會拋出OutOfMemoryError(記憶體溢出)異常;
本地方法棧:本地方法棧則是虛擬機器使用到的Native方法服務;
本地方法棧與虛擬機器棧一樣,也有2個異常情況:
1、如果線程請求的棧深度大於虛擬機器所允許的深度,將會拋出StackOverflowError異常;
2、如果虛擬機器可以動態擴充,如果擴充時無法申請到足夠的記憶體,將會拋出OutOfMemoryError異常; Java堆:
虛擬機器管理的記憶體中最大的一塊,同時也是被所有線程所共用的,它在虛擬機器啟動時建立,此記憶體地區的唯一目的就是存放對象執行個體,幾乎所有的對象執行個體以及數組都要在這裡分配記憶體。Java堆是垃圾收集器管理的主要區域,這裡面的對象被自動管理,也就是俗稱的 GC( Garbage Collector)所管理。Java堆的容量可以是固定大小,也可以隨著需求動態擴充( -Xms和-Xmx ),並在不需要過多空間時自動收縮。Java堆所使用的記憶體不需要保證是物理連續的,只要邏輯上是連續的即可。JAVA堆的分類:從記憶體回收的角度看, java堆可細分為:新生代、老年代,再細一點還可劃分為 Eden空間、From Survivor 空間、To Survivor空間等。從記憶體配置的角度來看,線程共用的Java堆中可能劃分出多個線程私人的分配緩衝區(TLAB);JVM實現應當提供給程式員調節 Java 堆初始容量的手段,對於可動態擴充和收縮的堆來說,則應當提供調節其最大和最小容量的手段。如果堆中沒有記憶體完成執行個體分配並且堆也無法擴充,就會拋 OutOfMemoryError。
方法區:在JAVA 虛擬機器規範中,將方法區作為堆的一個邏輯部分來對待,但事實上,方法區並不是堆,另外,不少人將 Java GC 的分代收集機制分為 3個代:青年代,老年代,永久代,這些作者將方法區定義為 “ 永久代” ,這是因為對於之前的 HotSpot 虛擬機器的實現方式中,將分代收集的思想擴充到了方法區,並將方法區設計成了永久代。不過除 HotSpot 之外的多數虛擬機器,並不將方法區當作永久代, HotSpot 本身也計劃取消永久代。
跟堆一樣是被各個線程共用的記憶體地區 ,用於儲存以被虛擬機器載入的類資訊、常量、靜態變數、即時編譯器編譯後的代碼等資料。雖然這個地區被虛擬機器規範把方法區描述為堆的一個邏輯部分,但是它的別名叫非堆,用來與堆做一下區別。
方法區在虛擬機器啟動的時候建立。
方法區的容量可以是固定大小的,也可以隨著程式執行的需求動態擴充,並在不需要過多空間時自動收縮。
方法區在實際記憶體空間中可以是不連續的。
Java虛擬機器實現應當提供給程式員或者終端使用者調節方法區初始容量的手段,對於可以動態擴充和收縮方法區來說,則應當提供調節其最大、最小容量的手段。
一般方法區上執行的垃圾收集是很少的,這也是方法區被稱為永久代的原因之一( HotSpot ),但這也不代表著在方法區上完全沒有垃圾收集,其上的記憶體回收目標主要是針對常量池的回收和對類型的卸載。
當方法區無法滿足記憶體配置需求時就會拋 OutOfMemoryError 。 運行時常量池:
它是方法區的一部分。
Class檔案中除了有類的版本、欄位、方法、介面等描述等資訊外,還有一項資訊是常量池(Constant Pool Table),用於存放編譯期產生的各種字面量和符號引用、翻譯出來的直接引用(符號引用就是編碼是用字串表示某個變數、介面的位置,直接引用就是根據符號引用翻譯出來的地址,將在類連結階段完成翻譯);這部分內容將在類載入後存放到方法區的運行時常量池中。
Java虛擬機器對Class檔案的每一部分(自然也包括常量池)的格式都有嚴格的規定,每一個位元組用於儲存哪種資料都必須符合規範上的要求,這樣才會被虛擬機器認可、裝載和執行。但對於運行時常量池,Java虛擬機器規範沒有做任何細節的要求,不同的供應商實現的虛擬機器可以按照自己的需要來實現這個記憶體地區。不過,一般來說,除了儲存Class檔案中描述的符號引用外,還會把翻譯出來的直接引用也儲存在運行時常量池中。
運行時常量池相對於Class檔案常量池的另外一個重要特徵是具備動態性,Java語言並不要求常量一定只能在編譯期產生,也就是並非預置入Class檔案中常量池的內容才能進入方法區運行時常量池,運行期間也可能將新的常量放入池中,這種特性被開發人員利用得比較多的便是String類的intern()方法。
既然運行時常量池是方法區的一部分,自然會受到方法區記憶體的限制,當常量池無法再申請到記憶體時會拋出OutOfMemoryError異常; 直接記憶體:
直接記憶體並不是虛擬機器運行時資料區的一部分,也不是Java虛擬機器規範中定義的記憶體地區。但是這部分記憶體也被頻繁使用,而且也會導致OutOfMemoryError異常;
在JDK1.4中新加入了NIO類,引入了一種基於通道與緩衝區的I/O方式,它可以使用Native函數庫直接分配堆外記憶體,然後通過一個儲存在Java堆中的DirectByteBuffer對象作為這塊記憶體的引用進行操作。
這樣能在一些情境中顯著提高效能,因為避免了在Java堆和Native堆中回來複製資料。 HotSpot虛擬機器對象探秘
虛擬機器遇到一條new指令時
1. 首先將去檢查這個指令的參數是否能在常量池中定位到一個類的符號引用,並且檢查這個符號引用代表的類是否已被載入、解析和初始化過。如果沒有,那必須先執行相應的類載入過程
2. 在類載入檢查通過之後,接下來虛擬機器將為新生對象分配記憶體。對象所需要的記憶體大小在類載入完成後便可以完全確定,為對象分配空間的任務等同於把一塊確定大小的記憶體從Java堆中劃分出來。
如果Java堆中的記憶體是絕對規整的,可以使用“指標碰撞”的分配方式(用過的記憶體放在一邊,閒置記憶體放在一邊,中間放著一個指標作為分界點的指標,分配記憶體就是僅僅將中間的指標向空閑記憶體那邊挪動一段與對象大小相等的距離)。;
如果Java堆中的記憶體並不規整,可以使用“空閑列表”分配方式(用過的記憶體和閒置記憶體相互交錯,虛擬機器就必須維護一個列表,記錄哪些記憶體塊是可用的,在分配的時候從列表中找到一塊足夠大的空間劃分給對象樣本,並更新列表上的記錄);
選擇那種分配方式是由Java堆中記憶體是否規整決定的。Java堆中記憶體是否規整是由所採用的垃圾收集器是否帶有壓縮整理功能決定的。
除了劃分可用空間之外,還要考慮對象建立在虛擬機器中是否頻繁,在並發情況下也並不是安全執行緒的。
解決這個問題有兩個解決方案:一種是對分配記憶體空間的動作進行同步處理—實際上虛擬機器採用CAS配上失敗重試的方式保證更新操作的原子性;另一種是把記憶體配置的動作按照線程劃分在不同的空間之中進行,即每個線程在Java堆中預先分配一小塊記憶體,稱為本地線程分配緩衝(TLAB)。
3. 記憶體配置完成之後,虛擬機器需要將分配到的記憶體空間都初始化為零(不包括對象頭)。
如果使用TLAB,這一項工作過程也可以提前至TLAB分配時進行。
這步操作保證了對象的執行個體欄位在Java代碼中可以不賦初始值就直接使用,程式能訪問到這些欄位的資料類型所對應的零值。
4. 虛擬機器要對對象進行必要的設定,例如這個對象是哪個類的執行個體、如果才能找到類的中繼資料資訊、對象的雜湊碼、對象的GC分代年齡等資訊,這些資訊存放在對象的對象頭中。 對象的記憶體布局
HotSpot虛擬機器中,對象在記憶體中儲存的布局可以分為:對象頭,執行個體資料和對齊填充;
對象頭分為2個部分:一部分用於儲存物件自身的運行資料,如雜湊碼、GC分代年齡、鎖狀態標誌等;另一部分是類型指標,即對象指向它的類別中繼資料的指標,虛擬機器通過這個指標來確定這個對象時哪個類的執行個體。
執行個體資料部分是對象真正儲存的有效資訊,也是在程式碼中所定義的各種類型的欄位內容。這部分的儲存順序會受到虛擬機器分配策略參數和欄位在Java源碼中定義的順序的影響
對象填充並不是必然存在,也沒有特別的含義。因為HotSpot虛擬機器的自動記憶體管理系統要求對象起始地址必須是8位元組的整數倍,也就是說對象的大小必須是8位元組的整數倍。
當對象執行個體資料部分沒有對齊時,就需要通過對象填充來補全。 對象的訪問定位
對象的訪問方式時取決於虛擬機器實現而定的。目前主流的訪問方式有控制代碼和直接指標兩種
控制代碼:使用控制代碼,那麼Java堆中就要分出一塊記憶體作為控制代碼池。
reference中儲存的是對象的控制代碼地址,而控制代碼中包含了對象執行個體資料和物件類型資料各自的具體地址資訊;
優點:reference中儲存的是穩定的控制代碼地址,在對象被移動時只會改變控制代碼中的執行個體資料指標,而reference本身不需要修改;
直接指標:如果使用直接指標方式,那麼Java堆對象的布局中就必須考慮如何放置訪問類型資料的資訊。 reference中儲存的直接就是對象的地址; 優點:避免了一次指標定位的時間開銷,速度快。
Java虛擬機器-java記憶體地區