Java是一門面向對象的語言,在Java程序運行的過程當中無時無刻都有對象被建立出來。在語言層面上,建立對象一般僅僅是一個new關鍵字而已,而在虛擬機中,對象(本文討論僅限於普通Java對象,不包括數組和Class對象)的建立又是一個怎樣的過程呢?程序員
當虛擬機須要一條new指令時,首先會去檢查這個指令的參數是否能在常量池中定位到一個類的符號引用,而且檢查這個符號引用是否已經被加載、解析初始化過。若是沒有,那必須先執行相應的類加載過程。 數組
在類加載檢查經過後,接下來虛擬機將爲新生對象分配內存(所需內存大小在類加載完成後遍可徹底肯定)。所謂分配內存,就等同於把一塊肯定大小的內存從Java堆中劃分出來(對象實例存在於JVM內存區域的堆區域)。根據Java堆中的內存是否規整,將分配方式分爲兩種:安全
內存規整的含義是,全部用過的內存都放在一邊,空閒的內存放在另外一邊,中間放着一個指針做爲分界點的指示器,那麼分配內存就僅僅是把那個指針向空閒空間那邊挪動一段與對象大小相等的距離。併發
內存不規整的含義是,已使用的內存和空閒的內存相互交錯。若是是這樣那就無法簡單的進行指針碰撞了,那麼分配內存的方式就變爲,虛擬機維護一個列表,記錄哪些內存是可用的,哪些內存是不可用的,在分配的時候從列表中找到一塊足夠大的空間劃分給對象實例,並更新列表上的記錄。佈局
注: 選擇哪一種分配方式是由Java堆是否規整而決定,而Java堆是否規整又由所採用的的垃圾收集器是否帶壓縮整理功能決定。線程
在建立對象的時候有一個很重要的問題,那就是線程安全,由於在實際開發中,建立對象是很頻繁的事,即便是僅僅修改一個指針所指向的位置,在併發狀況下也並非線程安全的,可能出現正在給對象A分配內存,指針還沒來得及修改,對象B又同時使用了原來的指針來分配內存狀況。指針
解決上述爲題一般有兩種方案:code
CAS是樂觀鎖的一種實現方式。所謂樂觀鎖就是,每次不加鎖而是假設沒有衝突而去完成某項操做,若是由於衝突失敗就重試,直到成功爲止。虛擬機採用CAS配上失敗重試的方式來保證更新操做的原子性。cdn
爲每個線程預先在Eden區分配一塊內存,JVM在給線程中的對象分配內存時,首先在TLAB分配,當對象大於TLAB中的剩餘內存TLAB內存已用盡時,再採用第一種方法(CAS+失敗重試)進行內存分配。對象
內存分配完成以後,虛擬機須要將分配到的內存空間都初始化爲零值(不包括對象頭),這一步操做保證了對象的實例字段在Java代碼中能夠不賦初始值就能直接使用,程序能訪問到這些字段的數據類型所對用的零值。
初始化零值完成以後,虛擬機要對對象進行必要的設置,例如這個對象是哪一個類的實例、若是才能找到類的元數據信息、對象的哈希碼、對象的GC分代年齡等信息。這些信息都存在對象頭中。另外,根據虛擬機當前運行狀態的不一樣,如是否啓用偏向鎖等,對象頭會有不一樣的設置方式。
在上面的工做都完成以後,從虛擬機的視角來看,一個新的對象已經產生了,可是從Java程序的角度來看,對象的建立纔剛剛開始,<init>
方法尚未執行,全部的字段都爲零。因此通常來講,執行new指令以後會接着指向<init>
方法,把對象按照程序員的意願進行初始化,這樣一個真正可用的對象纔算徹底產生出來。
在HotSpot虛擬機中,對象的內存中存儲的佈局能夠分爲3塊區域:對象頭、實例數據、和對其填充。
HotSpot虛擬機的對象頭包括兩部分信息,第一部分: 用於存儲對象自身的自身運行數據(哈希碼、GC分代年齡、鎖狀態標誌燈);第二部分: 是類型指針,即對象指向它的類元數據的指針,虛擬機經過這個指針來肯定這個對象是哪一個類的實例。
實例數據是對象真正存儲的有效信息,也是在程序中所定義的各類花類型的字段內容。
對其填充部分不是必然存在的,也沒有什麼特別的含義,僅僅起佔做用。 由於Hotspot虛擬機的自動內存管理系統要求對象起始地址必須是8字節的整數倍,換句話說就是對象的大小必須是8字節的整數倍。而對象頭部分正好是8字節的倍數(1倍或2倍),所以,當對象實例數據部分沒有對齊時,就須要經過對齊填充來補全。
創建對象就是爲了使用對象,咱們Java程序經過棧上的reference數據來操做堆上的具體對象。對象的訪問方式由虛擬機實現,目前主流的訪問方式有兩種:句柄訪問、直接指針。
若是使用句柄訪問的話,那麼Java堆中將會劃分出來一塊內存做爲句柄池,reference中存儲的就是對象的句柄地址,而句柄中包含了對象實例數據與類型數據各自的具體地址信息。以下圖所示。
若是使用直接指針訪問,那麼Java堆對象的佈局中就必須考慮如何放置訪問類型數據的相關信息,而reference中存儲的直接就是對象地址。以下圖所示。
二者優缺點:
訪問方式 | 優勢 | 缺點 |
---|---|---|
句柄訪問 | reference中存儲的是穩定的句柄地址,在對象被移動時只會改變句柄中的實例數據指針,而 reference 自己不須要修改 | 速度相對較慢 |
直接指針 | 速度快,它節省了一次指針定位的時間開銷 | 因爲對對象的訪問在Java總很是頻繁,所以這類開銷極少成多以後,也是很大的成本 |