虛擬機運行時數據區域描述了虛擬機管理的內存劃分狀況,可是目前咱們對於虛擬機仍是有不少困惑,好比:java
爲了搞清楚這些問題,咱們先從虛擬機是如何建立對象開始講起。算法
當虛擬機遇到一條new 指令時,便會進行對象的建立過程。數組
建立對象的過程以下:安全
若是沒有被加載過,則執行類加載過程,而後進入下一步; 若是已加載,則進入下一步。數據結構
(通過類加載後,類的信息被保存在方法區中,一個類的對象所需的內存大小也固定下來。)併發
內存分配完成以後,須要對分配的內存空間部分區域的內容都初始化爲零值。 這一步保證了對象成員變量在java代碼中能夠不賦初始值。學習
4.設置對象頭中的信息線程
關於對象頭是什麼, 別急,繼續往下看。3d
5.調用<init>
方法進行初始化指針
別再問<init>
是什麼了,先往下看。
在堆區分配內存有兩種方式。
若是堆中內存是規整的,即全部用過的內存都放在一邊,空閒的內存放在另外一邊,中間用一個指針作分界點的指示器。
分配內存的過程,實際上就是指針向空閒空間那邊移動一段與對象大小相等的距離。
java堆中的內存若是不是規整的,就須要使用空閒列表的分配方式。
空閒列表概念:虛擬機維護了一個列表,用於記錄哪些內存塊是可用的。
在分配的時候,從列表中找到一塊知足對象大小的內存空間劃分給對象實例,同時會更新列表上的記錄。
選擇哪一種分配方式取決於java堆是否規整。
而java堆是否規整取決於所採用的垃圾收集器是否帶有壓縮整理的功能。
所以,選擇哪一種分配方式最終取決於使用了哪一種垃圾收集器。
serial、ParNew等基於複製算法或標記整理(Mark Compact)算法的收集器,不會致使內存碎片,所以使用的是指針碰撞。
CMS等基於Mark-Sweep(標記清除)算法的收集器,會產生內存碎片,因此使用空閒列表法。
對象在內存中的數據除了實例自己的數據外,還包括對象頭和對齊填充
實例數據存儲的是成員變量的值,包括從父類繼承下來的成員變量。
成員變量在內存中的順序:相同寬度的字段會分配在一塊兒,父類定義的變量會出如今子類以前, 默認狀況下,子類中較窄的變量可能會被插入到父類變量的間隙中。反正就是不必定按定義的順序來分配。
對象頭的做用是記錄對象在運行過程當中所需的數據。
好比對象屬於哪一個類的實例、所屬類的信息在方法區中的位置(類型指針)、對象的哈希碼、對象的GC分代年齡等信息。這些信息就保存在對象頭中(Object Header)
對齊填充是用於確保對象的內存的總長度爲8字節的整數倍。
爲何要是確保是8字節的整數倍呢?
由於hotspot要求對象起始地址爲8字節的整數倍以便於自動內存管理, 換句話說,對象的總長度要爲8字節的整數倍才能保證如此。 而又由於對象頭正好是8字節(32位或64位)的整數倍,可是實例數據長度是任意的,所以須要對齊補充來確保整個對象總長度爲8字節的整數倍。
java程序須要經過引用來操做堆上的具體數據。 根據引用存放的地址類型的不一樣,對象有不一樣的訪問方式
主要有兩種訪問方式:
堆中會劃分一塊內存用來作句柄池。引用中存儲的就是對象的句柄地址。句柄包含了對象實例數據和對象類型的數據的指針。
經過引用訪問對象的時候,會首先根據引用找到對象的句柄,而後根據句柄中對象的地址來訪問對象。
引用中存儲的直接是對象的地址,直接經過引用來訪問對象。
使用句柄
使用直接指針
直接指針的速度快,hotspot採用就是直接指針的方式
對象分配內存不是線程安全的,好比給對象A分配內存,還沒來得及修改指針的指向, 另外一個線程建立對象B也用了原來的指針,這樣就會出問題的。
如何解決?
實際上虛擬機採用CAS配上失敗重試的方式保證更新指針操做的原子性。
即:每一個線程在java堆中預分配一小塊內存, 這一小塊內存稱做「本地線程分配緩衝"(Thread Local Allocation Buffer, TLAB)
內存分配的過程就能夠總結爲:不一樣線程使用指針碰撞或者空閒列表的方式在各自的TLAB
上分配內存。
當線程的TLAB
用完須要分配新的TLAB
,這時候才須要同步內存分配操做。
虛擬機是否須要使用TLAB
,能夠經過-XX:+/-UseTLAB
參數來決定。
從上面對象的建立過程,咱們能夠了解到,在內存分配完成以後,全部成員變量的值都還只是零值。
對於虛擬機來講,對象建立已經完畢,可是,對於java程序來講,對象的初始化纔剛開始。
成員變量的初始化工做交由<init>
方法的來完成。
編譯器收集了成員變量上的賦值操做,實例初始化代碼塊的賦值操做,以及構造方法中的賦值操做,構成了<init>
方法,並執行,對象就獲得了初始化。
學習過java基礎的人都知道,對象初始化的順序爲: 成員變量上的賦值-->實例初始化塊-->構造方法。
<init>
方法就解釋了爲何是這個過程。
對象頭的內存模型分三部分:
存放hashCode、GC分代年齡、鎖狀態標誌、線程持有的鎖、偏向線程id、偏向時間戳等。 長度爲32位或者64位(32位虛擬機和64位虛擬機)。
mark word是一個非固定的數據結構,在不一樣狀況下結構會有所變化。
好比:在32位的虛擬機中,若是對象處於未被鎖定的狀態, mark Word的32位空間將有25位用於存儲hashcode, 4位用於存儲對象的分代年齡,2位用於存儲對象上鎖 標誌,1位固定爲0
這些東西我就不一個個介紹他們是用來幹嗎的,講多了反而複雜,大概瞭解就行,有興趣的能夠百度。
一個指向類元數據的指針,經過這個指針,能夠肯定對象是哪一個類的實例。記住,這個指針是在對象頭中,但不是在Mark Word中的。
若是對象是一個數組,在對象頭中還必須有一塊用於記錄數組長度的數據。
這一部分僅在對象是數組的時候存在。
點贊是對我最大的鼓勵