虛擬機遇到一條new指令時,首先將去檢查這個指令的參數是否能在常量池中定位到一個類的符號引用,而且檢查這個符號引用表明的類是否已被加載、解析和初始化過。若是沒有,那必須先執行相應的類加載過程。算法
在類檢查經過後,接下來虛擬機將爲新生對象分配內存。對象所需內存的大小在類加載完成後便徹底肯定,爲對象分配空間的任務等同於把一塊肯定大小的內存從Java堆中劃分出來。數組
內存分配方式:併發
指針碰撞oop
假設Java堆中內存是絕對規整的,全部用過的內存都放在一邊,空閒的內存放在另外一邊,中間放着一個指針做爲分界點的指示器,那所分配內存就僅僅是把那個指針向空閒空間那邊挪動一段與對象大小相等的距離。佈局
空閒列表線程
若是Java堆中的內存並非規整的,已使用的內存和空閒的內存相互交錯,那就沒有辦法簡單地進行指針碰撞了,虛擬機就必須維護一個列表,記錄上哪些內存塊是可用的,在分配的時候從列表中找到一塊足夠大的空間劃分給對象實例,並更新列表上的記錄。指針
選擇哪一種分配方式由Java堆是否規整決定,而Java堆是否規整又由所採用的垃圾收集器是否帶有壓縮整理功能決定。code
所以,在使用Serial、ParNew等帶有Compact過程的收集器時,系統採用的分配算法是指針碰撞,而使用CMS這種基於Mark-Sweep算法的收集器時,一般採用空閒列表。cdn
在併發時分配內存:對象
內存分配完成後,虛擬機須要將分配到的內存都初始化爲零值(不包括對象頭),若是使用TLAB,這一工做過程也能夠提早至TLAB分配時進行。這一步操做保證了對象的實例字段在Java代碼中能夠不賦予初始值就直接使用,程序能訪問到這些字段的數據類型所對應的零值。
設置對象頭等其餘信息
執行<init>
方法
對象頭分爲兩個部分
第一部分用於存儲對象自身的運行時數據。如哈希碼、GC分代年齡、鎖狀態標誌、線程持有的鎖、偏向線程ID、偏向時間戳等。這部分數據的長度在32位和64位虛擬機中分別爲32bit和64bit,官方成爲Mark Word。
另外一部分是類型指針,即對象指向它的類元數據的指針,虛擬機經過這個指針來肯定這個對象是哪一個類的實例。若是對象是一個Java數組,那在對象頭中還必須有一塊用於記錄數組長度的數據,由於虛擬機能夠經過普通Java對象的元數據信息肯定Java對象的大小,可是從數組的元數據中卻沒法肯定數組的大小。
實例數據部分是對象真正存儲的有效信息,也是在程序代碼中所定義的各類類型的字段內容。不管是從父類繼承下來的,仍是在子類中定義的,都須要記錄起來。
這部分存儲順序會受到虛擬機分配策略參數和字段在Java源碼中定義順序的影響。HotSpot虛擬機默認的分配策略爲longs/doubles、ints、shorts/chars、bytes/booleans、oops,相同的寬度的字段老是被分配到一塊兒。
對其填充並非必然存在的,沒有特別的含義,僅僅起着佔位符的做用,因爲HotSpot VM的自動管理系統要求對象起始地址必須是8字節的整數倍,換句話說就是對象的大小必須是8字節的整數倍。
Java程序經過棧上的Reference引用來操做堆上的具體對象。因爲Reference類型在Java虛擬機規範中只規定了一個指向對象的引用,並無定義這個引用應該經過何種方式去定位、訪問堆中的對象的具體位置,因此對象訪問方式也是取決於虛擬機實現而定的。目前主流的訪問方式有使用句柄和直接指針關聯。
使用句柄訪問,那麼Java堆中將會劃出一塊內存來做爲句柄池,Reference中存儲的就是對象的句柄地址,而句柄中包含了對象實例數據與類型數據各自的具體地址信息。
直接指針訪問,那麼Java堆對象的佈局中就必須考慮如何放置訪問類型數據的相關信息,而Reference中存放的直接就是對象地址。
使用句柄來訪問的最大好處就是Reference中存儲的是穩定的句柄地址,在對象被移動(垃圾收集是移動對象是很是廣泛的行爲)時只會改變句柄中的實例數據指針,而Reference自己不須要修改。
使用直接指針訪問方式的最大好處就是速度更快,他節省了一次指針定位的時間開銷,又有對象的訪問在Java中很是頻繁,所以這類開銷聚沙成塔後也是一項很是可觀的執行成本。HotSpot虛擬機是使用第二種方式進行對象訪問的。