深刻學習Java虛擬機——虛擬機內存區域與內存溢出異常

1. 運行時數據區域

 

1.1 程序計數器

    1. 程序計數器是一段較小的內存空間,能夠看做爲當前線程所執行字節碼的行號指示器。經過改變這個計數器的值來選取下一條字節碼指令,分支、循環、跳轉、異常處理、線程恢復等基礎功能都須要該計數器。程序員

    2. 每條線程都會有一個獨立的程序計數器,各線程間程序計數器互不影響,獨立存儲,因此這個內存區域是線程私有的算法

    3. 若是線程正在執行的是一個Java方法,那麼計數器記錄的是正在執行的虛擬機字節碼指令的地址,若是執行的是本地(Native)方法,則這個計數器值爲空,此內存區域是惟一一個在Java虛擬機中沒有OutOfMemoryError狀況的區域數組

1.2 虛擬機棧

    1. 首先,虛擬機棧是線程私有的,它的生命週期與線程相同。虛擬機棧描述的是Java方法的執行模型:每一個方法在執行的同時都會建立一個棧楨,用於存儲局部變量表,操做數棧,動態連接,方法出口等信息。每個方法從調用直至執行完成,就對應着一個棧楨從入棧到出棧的過程。安全

    2. 局部變量表存放了編譯器可知的各類基本數據類型(boolean,byte,char,short,int,long,float,double)、對象引用類型和returnAddress類型(指向一條字節碼指令的地址)。且局部變量表的內存空間會在編譯期完成分配,方法運行期間不會改變局部變量表的大小。數據結構

    3. 異常情況:併發

(1)若是線程請求的棧深度大於虛擬機所容許的深度,則將拋出StackOverflowError異常函數

(2)若是虛擬機棧能夠動態擴展,而擴展時沒法申請的足夠的內存,就會拋出OutOfMemoryError異常佈局

1.3 本地方法棧

    1. 本地方法棧與虛擬機棧相似,但虛擬機棧是爲虛擬機執行Java方法服務的,而本地方法棧是爲虛擬機使用的本地方法服務。性能

    2. 一樣的,該內存區域也會有StackOverflowError異常和OutOfMemoryError異常。spa

1.4 Java堆

    1. Java堆是被全部線程共享的內存區域,在虛擬機啓動時建立。此區域只用來存儲對象實例,幾乎全部的對象都會在這裏被建立(並非全部的對象都在堆中建立)。

    2. Java堆是垃圾收集器管理的主要區域。從內存回收角度看,垃圾收集器主要採用分代收集算法,因此還能夠將Java堆分爲新生代和老年代,進一步細分爲Eden區,From Survivor區和To Survivor區。從內存分配的角度看,線程共享的Java堆中可能劃分出多個線程私有的分配緩衝區。

    進行這些劃分的目的都是爲了更快更好的回收內存或者分配內存

    3. 可能發生的異常:Java堆可能會處於物理上內存空間不連續的內存空間中,但邏輯上必須是連續的。其空間大小能夠經過-Xmx和-Xms來控制,能夠實現爲固定大小,也能夠爲可擴展大小。當沒有足夠的內存空間完成分配而且堆沒法擴展時,就會拋出OutOfMemoryError異常

1.5 方法區

    1. 方法區是線程共享的內存區域,它用於存儲虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據,方法區屬於堆的一個邏輯部分,但它卻仍然要與Java堆區分開。

    2. 方法區與堆相似,不須要連續的內存空間、內存空間大小能夠固定或者可擴展,還能夠選擇不實現垃圾收集。在方法區,不多出現垃圾收集,這個區域的內存回收主要針對常量池的回收以及對類型的卸載。

    3. 當方法區沒法知足內存分配需求時,就會拋出OutOfMemoryError異常

    4. 運行時常量池:該區域是方法區的一部分,Class文件中除了有相似的版本、字段、方法、接口等描述信息外,還有一項信息是常量池,用於存放編譯器生成的各類字面量和符號引用,這部份內容將在類被加載後進入方法區的運行時常量池中存放。運行時常量池相對於Class文件常量池的一個重要特徵是具有動態性,即運行期間也可能將新的常量放入運行時常量池,好比String類中的intern()方法。

1.6 直接內存

    1.直接內存並非虛擬機運行時數據區的一部分,也不是Java虛擬機規範中定義的內存區域。

    2.應用:在jdk1.4之後加入了NIO類,引入了一種基於通道與緩衝區的新IO方式,它使用Native函數庫直接分配堆外內存,而後經過一個存儲在Java堆中DirectByteBuffrer對象做爲這塊內存的引用進行直接操做,避免了在Java堆與Native堆之間來回複製數據,顯著提升了性能。因爲分配的是Native內存空間,因此大小不會受到Java堆大小的限制,可是確定會受到本機總內存的限制。在經過設置虛擬機參數來設置堆等區域的內存空間時,忽略直接內存大小就有可能致使各個內存區域總和大於物理內存限制,從而致使動態擴展時出現OutOfMemoryError異常

2. Java虛擬機對象

2.1 對象的建立

    1. 建立過程(通常對象,即不包括數組和Class對象):

(1)加載對象類:當虛擬機運行一條new指令時,首先檢查這個指令的參數(也就是new後面的類名)是否能在常量池中定位到一個類的符號引用,而且檢查這個類是否已被加載、解析和初始化過。若是沒有,那就必須進行相應的類加載過程(該過程在後面會詳細分析)。

(2)分配對象所需內存空間:

    在類加載完成後便可肯定對象所需的內存空間大小,給對象分配內存空間就是把一塊肯定大小的內存從Java堆中劃分出來。分配方式主要取決於虛擬機所採用的GC是否帶有壓縮整理功能。

    有壓縮整理功能的GC會把Java堆分紅兩部分,一部分是被佔用的,一部分是空閒的,經過一個指針做爲分界的指示器,分配內存是隻須要把指針向空閒區移動便可;不帶壓縮整理功能的GC會致使Java堆處於一種空閒與佔用交錯的內存空間,這時虛擬機就必須維護一個列表,記錄堆中可用的內存空間,分配時只須要找到一個足夠大小的空間劃分給對象便可。

    可是,在併發狀況下,以上兩種方式也並不安全,好比,正在給對象A分配空間時,指針還未修改,對象B又佔用了該指針來分配空間。因此,虛擬機採用了CAS加上失敗重試的方式保證更新操做的原子性;另外一種方式是把內存分配動做按照線程劃分在不一樣的空間中進行,即每一個線程在堆中先分配一小塊內存,稱爲本地線程分配緩衝(TLAB),那個線程要分配內存,就在那個線程的TLAB上分配,當使用完TLAB並分配新的TLAB時,才須要同步鎖定,虛擬機使用TLAB可經過 -XX:+/-UseTLAB參數來設定。

(3)內存空間初始化:內存空間分配後,虛擬機須要將分配到的內存空間都初始化爲零值(不包括對象頭),若是使用TLAB,這一過程也會提早至TLAB分配時執行。該過程保證了對象即便不賦予初始值額可使用,能訪問到的字段的數據類型均爲所對應的零值。

(4)設置對象信息:例如這個對象是哪一個類的實例,如何找到類的元數據信息,對象的哈希碼,對象的GC分帶年齡等信息。這些信息存放在對象的對象頭中,根據虛擬機當前運行狀態的不一樣,是否啓用偏向鎖等。

(5)對象數據初始化:以上步驟完成後,對象已經建立成功,但此時對象內全部字段爲零或null,此時便進行對象數據初始化,創造程序員所須要的對象。

2.2 對象的內存佈局

虛擬機中,對象在內存中存儲的佈局能夠分爲3塊區域:對象頭,實例數據,對齊填充。

    1. 對象頭:包括兩部分信息,第一部分用於存儲對象自身的運行時數據,如哈希碼,GC分代年齡,鎖狀態標誌,線程持有的鎖,偏向線程ID,偏向時間戳等,這部分數據被稱爲「Mark  Word」,這部分數據長度在32或64位的虛擬機中分別爲32bit或64bit。對象須要存儲的運行時數據不少,超出32或64bit所能記錄的限度,但對象頭信息是與對象自身定義的數據無關的額外存儲成本,Mark  Word被設計爲一個非固定的數據結構以便在極小的空間內存儲儘可能多的信息,例如在32位的HotSpot虛擬機中,若是對象處於未被鎖定的狀態下,則Mark  Word的32bit空間中,25bit用於存儲對象哈希值,4bit用於存儲對象分帶年齡,2bit用於存儲鎖標誌位,1bit固定爲0,其餘狀態下的存儲內容以下

存儲內容 標誌位 狀態
對象哈希碼、對象分代年齡、 01 未鎖定
指向鎖記錄的指針 00 輕量級鎖定
指向重量級鎖的指針 10 重量級鎖定
11 GC標記
偏向線程id、偏向時間戳、對象分代年齡 01 可偏向

    對象頭的另外一部分數據是類型指針,即對象指向他的類元數據的指針,虛擬機經過該指針肯定對象所屬的類,但並非全部的虛擬機實現都必須在對象數據上保留類型指針,也就是說查找對象的元數據信息並不必定要通過對象自己。對於數組對象,對象頭中還必須友誼路愛用於記錄數組長度的數據。

    2.實例數據:這裏是對象真正存儲的有效信息,包括各個字段的內容,不管是當前類的仍是父類的。

    3.對齊填充:這一部分並非必要存在的,也沒有特殊含義,僅僅是爲了使對象的大小必須是8字節的整數倍,若是對象的大小不足,則會進行填充補全。

2.3 對象的訪問定位

建立對象是爲了使用對象,在Java程序中,經過棧上的對象引用來操做堆上的對象,那麼這個引用經過何種方式去定位和訪問堆中的對象的具體位置?具體實現取決於虛擬機實現,主要有兩種方法,使用句柄和直接指針。

    1.使用句柄訪問:Java堆中劃分一塊內存做爲句柄池,reference(引用)中存儲的就是句柄池中存儲的對象的句柄地址,而句柄包含了對象的實例數據與類型數據各自的具體地址信息。

    2.使用直接指針訪問:reference(引用)中存儲的就是對象地址

這兩種訪問方式各有優點,使用句柄訪問的好處是引用中存儲的是穩定的句柄地址,對象被移動時只會改變句柄中的示例數據指針,而引用自己不須要修改。而使用直接指針訪問方式的最大好處是速度更快,大部分虛擬機都會採用這種方式。

相關文章
相關標籤/搜索