Java虛擬機-java內存區域

運行時數據區域
 
Java虛擬機在執行Java程序的過程當中會把它所管理的內存劃分爲若干個大小不一樣的數據域;
這些區域都有各自的用途,以及建立和銷燬時間,有的區域會隨着虛擬機進程的啓動而存在,有些區域則依賴用戶線程的啓動和結束而創建和銷燬。

 
程序計數器:
程序計數器是一塊較小的內存空間,能夠看做當前線程所執行的字節碼的 信號指示器;
因爲Java虛擬機的多線程是經過線程輪流轉換並分配處理執行時間的方式來實現的,在任何一個肯定的時刻,一個處理器都只會執行一條線程中的指令。所以,爲了線程切換後能恢復到正確的執行位置, 每條線程都須要一個獨立的程序計數器
各條線程之間的程序計數器互不影響,獨立存儲,這類內存區域爲「線程私有」的內存;
 
Java虛擬機棧:
Java虛擬機棧也是 線程私有 的,它的生命週期與線程相同。
虛擬機棧爲虛擬機執行Java方法服務
虛擬機棧描述的是Java方法執行的內存模型:每一個方法在執行的同時都會建立一個棧幀用於 存儲局部變量表、操做數棧、動態連接、方法出棧口 等信息。
每個方法從調用直至執行完成的過程,就對應着一個棧幀在虛擬機棧中從進棧到出棧的過程

局部變量表存放了編譯期可知的各類基本數據類型(boolean、byte、char、short、int、float、long、double)、對象引用(reference類型)和returnAddress類型;
其中  64 位長度的 long  double 類型會佔用  2 個局部變量空間,其他數據類型只佔用  1 個。
局部變量表所須要的內存空間在編譯期間完成分配。

在Java虛擬機規範中,對這個區域規定了2中異常狀況:
一、若是線程 請求的棧深度大於虛擬機所容許的深度 ,將會拋出 StackOverflowError 棧溢出 )異常;
二、若是虛擬機棧能夠動態擴展,若是 擴展時沒法申請到足夠的內存 ,將會拋出 OutOfMemoryError (內存溢出)異常;

本地方法棧:
本地方法棧則是虛擬機使用到的Native方法服務;
本地方法棧與虛擬機棧同樣,也有2個異常狀況:
一、若是線程請求的棧深度大於虛擬機所容許的深度,將會拋出StackOverflowError異常;
二、若是虛擬機能夠動態擴展,若是擴展時沒法申請到足夠的內存,將會拋出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中存儲的直接就是對象的地址;
 
優勢 :避免了一次指針定位的時間開銷,速度快。
相關文章
相關標籤/搜索