文章目錄
前言
上一篇咱們介紹了JVM的內存區域佈局,而且重點介紹了堆和棧的概念。,今天咱們接着來學習JVM的對象建立過程已經對象的訪問方式。程序員
對象建立
對象的建立共有如上五個步驟:
數組
1.類加載檢查
虛擬機遇到一條new指令時,首先將去檢查這個指令是否在常量池中定位到這個類的符號引用,而且檢查這個符號引用表明的類是否已經被加載過、解析和初始化過。若是沒有,那必須先執行相應的類加載過程。JVM中類加載是經過雙親委派模型來完成的雙親委派模型加載類。安全
2.分配內存
類加載檢查經過後,接下來虛擬機將爲新生成對象分配內存。對象所需的內存大小在類加載完成後即可肯定,爲對象分配空間的任務等同於把一塊肯定大小的內存從Java堆中劃分出來。分配方式有"指針碰撞"和"空閒列表"兩種,選擇那種分配方式由Java堆是否規整決定。而Java堆是否規整又由所採用的垃圾收集器是否帶有壓縮整理功能決定。併發
分配內存的方式
- 指針碰撞
經過"指針碰撞"分配內存的方式適用場合是
堆內存規整(即沒有內存碎片) 的狀況下。它的實現原理是:用過的內存所有整合到一邊,沒有用到的內存放在另外一邊,只須要向着沒用過的內存方向將該指針移動對象內存大小位置便可。使用這種方式分配內存的垃圾收集器有:Serial收集器和ParNew收集器。 - 空閒列表
經過"空閒列表"分配內存的方式適用場景是 堆內存不規整的狀況。它的實現原理是:虛擬機會維護一個列表,該列表中會記錄哪些內存塊是可用的,在分配的時候,找一塊足夠大的內存塊來劃分對象實例,最後更新列表記錄 。使用這種方式分配內存的垃圾收集器有:CMS收集器。
內存分配的併發問題
在實際項目中,建立對象是很頻繁的事情,虛擬機採用兩種方式來保證線程安全:oop
- CAS+失敗重試: CAS是樂觀鎖一種實現方式所謂樂觀鎖就是,每次不加鎖而是假設沒有衝突去完成某項操做,若是失敗就進行重試操做,直到重試成功。虛擬機採用CAS加上失敗重試的方式保證更新操做的原子性。
- TLAB: 爲每個線程預先在Eden區分配一塊內存,JVM在給線程中的對象分配內存時,首先在TLAB分配,當對象大於TLAB中剩餘內存或TLAB的內存已用盡時,再採用上述的CAS進行內存分配。
3.初始化零值
內存分配完成以後,虛擬機須要將分配到的內存空間都初始化爲零值(不包括對象頭),這一步操做保證了對象的實例字段在Java代碼中能夠不賦初始值就直接使用程序能訪問到這些字段的數據類型所對應的零值。佈局
4.設置對象頭:
初始化零值完成以後,虛擬機要對對象進行必要的設置,例如這個對象是哪一個類的實例、如何才能找到類的元數據信息、對象的哈希碼、對象的GC分代年齡等信息。這些信息存放在對象頭中。另外,根據虛擬機當前運行狀態的不一樣,是否啓用偏向鎖等,對象頭會有不一樣的設置方式。學習
5. 執行init方法;
在上面工做都完成以後,從虛擬機的視角來看,一個新的對象已經產生了,可是從Java程序的視角來看,對象建立纔剛開始,init方法尚未執行,全部的字段都還爲零,全部通常來講,執行new指令以後會接着執行init方法,把對象按照程序員的意願進行初始化,這樣一個真正可用的對象纔算完成產生出來。spa
對象內存佈局
對象在內存中的佈局能夠分3塊區域:對象頭(Header)、實例數據(Instance Data)和對齊填充(Padding)。.net
對象頭
虛擬機對象的對象頭部分包括兩類信息。
第一類是用於存儲對象自身的運行時數據,如哈希碼(HashCode)、GC分代年齡、鎖狀態標誌、線程持有的鎖、偏向線程ID、偏向時間戳等長度在32位和64位的虛擬機中分別爲32bit和64個bit,官方稱爲"Mark Word"。
例如在32位的HotSpot虛擬機中,如對象未被同步鎖鎖定的狀態下,Mark Word的32個比特存儲空間中的25個比特用於存儲對象哈希碼,4個比特用於存儲對象分代年齡,2個比特用於存儲鎖標誌位,1個比特固定爲0狀態(輕量級鎖定、重量級鎖定、GC標記、可偏向)下對象的存儲內容以下所示:
對象頭的另一部分是類型指針,即對象指向他的類型元數據的指針,Java虛擬機經過這個指針來肯定該對象是哪一個類的實例。並非全部的虛擬機實現都必須在對象數據上保留類型指針。還一句話說,查找對象的信息並不必定要通過對象自己,好比,若是對象是一個Java數組,那在對象頭中還必須有一塊用於記錄數組長度的數據,由於虛擬機能夠經過普通Java對象的元數據信息能夠肯定Java對象的大小,可是若是數組的長度是肯定的,將沒法經過元數據中的信息推斷數組的大小。
線程
實例數據
實例數據部分就是對象真正存儲的有效信息,咱們在程序代碼裏所定義的各類類型的字段內容,不管是從父類繼承下來的,仍是在子類中定義的字段都必須記錄下來。這部分的存儲順序會受到虛擬機分配策略參數(-XX: FieldsAllocationStyle參數)和字段在Java源碼中定義的順序的影響。虛擬機默認的分配順序爲 longs/doubles、ints、shorts/chars、bytes/booleans、oops(Ordinary Object Pointers,OOPs) ,從以上默認的分配策略中能夠看到,相同寬度的字段老是被分配到一塊兒存放,在知足這個前提條件的狀況下,在父類中定義的變量會出如今子類以前。若是虛擬機的+XX: CompactFields參數值爲true(默認就爲true),那子類之中叫窄的變量也容許插入父類變量的空隙之中,以節省一點點空間。
對齊填充
對齊填充這部分不是必然存在的,也沒有特別的含義,它僅僅起着佔位符的做用。
對象訪問方式
咱們的Java程序會經過棧上的reference數據來操做堆上的具體對象。對象的訪問方式也是由虛擬機實現的,主流的訪問方式主要有使用句柄和直接指針兩種。
使用句柄訪問
使用句柄訪問的話,Java堆中將會劃分出一塊內存做爲句柄池,reference中存儲的就是對象的句柄地址,而句柄中包含了對象實例數據與類型數據各自具體的地址信息。其結構以下圖所示:
使用直接指針訪問
使用直接指針訪問的話,Java堆中對象的內存佈局就必須考慮如何訪問類型數據的相關信息,reference中存儲的直接就是對象地址,若是隻是訪問對象自己的話,就不須要多一次間接的訪問的開銷。HosSpot而言,主要使用的是這種訪問方式。其結構以下圖所示:
使用直接指針來訪問最大的好處就是速度更快,它節省了一次指針定位的時間開銷,因爲對象訪問在Java中很是頻繁,所以這類開銷聚沙成塔也是一項極爲可觀的執行成本。
總結
本文首先介紹了JVM中對象的建立過程,接着就是介紹對象的內存佈局,最後就是說到了對象的訪問方式,其中對象的建立過程比較重要的一塊內容就是分配內存主要內容來自於《深刻理解Java虛擬機_JVM高級特性與最佳實踐_第3版》