十年網(wǎng)站開發(fā)經(jīng)驗(yàn) + 多家企業(yè)客戶 + 靠譜的建站團(tuán)隊(duì)
量身定制 + 運(yùn)營維護(hù)+專業(yè)推廣+無憂售后,網(wǎng)站問題一站解決
這期內(nèi)容當(dāng)中小編將會給大家?guī)碛嘘P(guān)java類對象底層是如何創(chuàng)建的,文章內(nèi)容豐富且以專業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。
創(chuàng)新互聯(lián)公司于2013年創(chuàng)立,先為灌南等服務(wù)建站,灌南等地企業(yè),進(jìn)行企業(yè)商務(wù)咨詢服務(wù)。為灌南企業(yè)網(wǎng)站制作PC+手機(jī)+微官網(wǎng)三網(wǎng)同步一站式服務(wù)解決您的所有建站問題。
0、前言
Java程序中 User user = new User();的代碼在執(zhí)行過程中,JVM究竟做了哪些工作?
1、Java類對象的創(chuàng)建過程
Java對象保存在內(nèi)存中時(shí),主要由三部分組成:對象頭、實(shí)例數(shù)據(jù)、對齊填充字段,所以Java對象創(chuàng)建的過程實(shí)際上是對這三部分進(jìn)行配置、補(bǔ)充和初始化的過程。
注:對齊填充字段:在JVM中,要求對象占用內(nèi)存的大小應(yīng)該是8bit的倍數(shù),這個(gè)信息是用來補(bǔ)齊8bit的,無其他作用
1.1、類加載檢查階段
虛擬機(jī)遇到一條new指令時(shí),首先將去檢查這個(gè)指令的參數(shù)是否能在常量池中定位到這個(gè)類的符號引用,并且檢查這個(gè)符號引用所代表的類是否已被加載過、連接過和初始化過。如果沒有則必須先執(zhí)行相應(yīng)的類加載過程。
1.2、分配內(nèi)存
類加載檢查通過后,JVM將為新創(chuàng)建對象分配內(nèi)存。對象所需的內(nèi)存大小在類加載完成后便可以確定。所以,為對象分配內(nèi)存空間相當(dāng)于把確定大小的內(nèi)存從Java堆中劃分出來。分配方式有"指針碰撞"和"空閑列表"兩種,選擇哪種分配方式由Java堆是否規(guī)整決定,而Java堆是否規(guī)整又由所采用的垃圾收集器是否具有壓縮整理功能所決定(標(biāo)記-清楚不規(guī)整,標(biāo)記-整理和復(fù)制都是規(guī)整的)。
1. 指針碰撞
適用場合:堆內(nèi)存規(guī)整(沒有內(nèi)存碎片)
原理:用過的內(nèi)存放一邊,沒用過的內(nèi)存放一邊,中間有一個(gè)分界值指針,分配內(nèi)存時(shí)只需要向著沒用過的內(nèi)存方向?qū)⒃撝羔樢苿?dòng)對象確定內(nèi)存大小位置即可
GC收集器:Serial、ParNew
2. 空閑列表
適用場合:堆內(nèi)存不規(guī)整
原理:JVM會維護(hù)一個(gè)列表,該列表會記錄哪些內(nèi)存塊是可用的,分配內(nèi)存時(shí)找一塊足夠大的內(nèi)存區(qū)域劃分給對象實(shí)例,最后更新內(nèi)存列表
GC收集器:CMS
內(nèi)存分配的并發(fā)問題
堆內(nèi)存是線程共享的,所以在創(chuàng)建對象分配內(nèi)存的時(shí)候一個(gè)重要的問題就是線程安全問題。JVM采用以下兩種方式保證線程安全。
1. CAS+失敗重試:CAS是樂觀鎖的一種實(shí)現(xiàn)方式。樂觀鎖,就是每次假設(shè)沒有沖突,不加鎖地去執(zhí)行操作。JVM采用CAS+失敗重試的方式保證更新操作的原子性。
2. 線程本地分配緩存(TLAB):JVM為每個(gè)線程預(yù)先在Eden區(qū)分配一小塊區(qū)域-線程本地分配緩存(TLAB),JVM在給線程中的對象分配內(nèi)存時(shí),首先在 TLAB中劃分內(nèi)存。當(dāng)對象大于TLAB剩余內(nèi)存或者TLAB用盡時(shí),JVM會再采用CAS+失敗重試當(dāng)方式分配內(nèi)存。
1.3、初始化零值
內(nèi)存分配完成后,虛擬機(jī)需要將分配到的內(nèi)存空間都初始化零值(不包括頭對象)。這一過程保證了Java的實(shí)例對象在JVM中可以不賦初始值就直接使用,程序能訪問這些字段的數(shù)據(jù)類型所對應(yīng)的零值。
1.4、設(shè)置頭對象
初始化零值后,JVM要對對象信息進(jìn)行必要的設(shè)置(與類的關(guān)聯(lián)關(guān)系、關(guān)聯(lián)類的元數(shù)據(jù)信息、對象的哈希碼、對象的GC分代年齡),這些信息存在對象的對象頭中。
1.4.1、頭對象的形式
JVM中對象頭的方式有以下兩種(以32位JVM為例)
1.4.1.1、普通對象
|--------------------------------------------------------------|
| Object Header (64 bits) |
|------------------------------------|-------------------------|
| Mark Word (32 bits) | Klass Word (32 bits) |
|------------------------------------|-------------------------|
1.4.1.2、數(shù)組對象
|------------------------------------------------------------------------------|
| Object Header (96 bits) |
|--------------------------------|-----------------------|----------------------|
| Mark Word(32bits) | Klass Word(32bits) | array length(32bits) |
|--------------------------------|-----------------------|----------------------|
1.4.2、頭對象組成
1. Mark Word
2. 指向類的指針
3. 數(shù)組長度(只有數(shù)組對象才有)
1.4.2.1、Mark Word
Mark Word記錄了對象和鎖有關(guān)的信息,當(dāng)這個(gè)對象被synchronized關(guān)鍵字當(dāng)成同步鎖時(shí),圍繞這個(gè)鎖的一系列操作都和Mark Word有關(guān)。
Mark Word在32位JVM中的長度是32bit,在64位JVM中長度是64bit。
JVM一般是這樣使用鎖和Mark Word的:
1,當(dāng)沒有被當(dāng)成鎖時(shí),這就是一個(gè)普通的對象,Mark Word記錄對象的HashCode,鎖標(biāo)志位是01,是否偏向鎖那一位是0。
2,當(dāng)對象被當(dāng)做同步鎖并有一個(gè)線程A搶到了鎖時(shí),鎖標(biāo)志位還是01,但是否偏向鎖那一位改成1,前23bit記錄搶到鎖的線程id,表示進(jìn)入偏向鎖狀態(tài)。
3,當(dāng)線程A再次試圖來獲得鎖時(shí),JVM發(fā)現(xiàn)同步鎖對象的標(biāo)志位是01,是否偏向鎖是1,也就是偏向狀態(tài),Mark Word中記錄的線程id就是線程A自己的id,表示線程A已經(jīng)獲得了這個(gè)偏向鎖,可以執(zhí)行同步鎖的代碼。
4,當(dāng)線程B試圖獲得這個(gè)鎖時(shí),JVM發(fā)現(xiàn)同步鎖處于偏向狀態(tài),但是Mark Word中的線程id記錄的不是B,那么線程B會先用CAS操作試圖獲得鎖,這里的獲得鎖操作是有可能成功的,因?yàn)榫€程A一般不會自動(dòng)釋放偏向鎖。如果搶鎖成功,就把Mark Word里的線程id改為線程B的id,代表線程B獲得了這個(gè)偏向鎖,可以執(zhí)行同步鎖代碼。如果搶鎖失敗,則繼續(xù)執(zhí)行步驟5。
5,偏向鎖狀態(tài)搶鎖失敗,代表當(dāng)前鎖有一定的競爭,偏向鎖將升級為輕量級鎖。JVM會在當(dāng)前線程的線程棧中開辟一塊單獨(dú)的空間,里面保存指向?qū)ο箧iMark Word的指針,同時(shí)在對象鎖Mark Word中保存指向這片空間的指針。上述兩個(gè)保存操作都是CAS操作,如果保存成功,代表線程搶到了同步鎖,就把Mark Word中的鎖標(biāo)志位改成00,可以執(zhí)行同步鎖代碼。如果保存失敗,表示搶鎖失敗,競爭太激烈,繼續(xù)執(zhí)行步驟6。
6,輕量級鎖搶鎖失敗,JVM會使用自旋鎖,自旋鎖不是一個(gè)鎖狀態(tài),只是代表不斷的重試,嘗試搶鎖。從JDK1.7開始,自旋鎖默認(rèn)啟用,自旋次數(shù)由JVM決定。如果搶鎖成功則執(zhí)行同步鎖代碼,如果失敗則繼續(xù)執(zhí)行步驟7。
7,自旋鎖重試之后如果搶鎖依然失敗,同步鎖會升級至重量級鎖,鎖標(biāo)志位改為10。在這個(gè)狀態(tài)下,未搶到鎖的線程都會被阻塞。
32位JVM中,Mark Word存儲示意:
其中無鎖和偏向鎖的鎖標(biāo)志位都是01,只是在前面的1bit區(qū)分了這是無鎖狀態(tài)還是偏向鎖狀態(tài)。
JDK1.6以后的版本在處理同步鎖時(shí)存在鎖升級的概念,JVM對于同步鎖的處理是從偏向鎖開始的,隨著競爭越來越激烈,處理方式從偏向鎖升級到輕量級鎖,最終升級到重量級鎖。
1.4.2.2、指向類的指針
該指針在32位JVM中的長度是32bit,在64位JVM中長度是64bit。
Java對象的類數(shù)據(jù)保存在方法區(qū)。
1.4.2.3、數(shù)組長度
只有數(shù)組對象保存了這部分?jǐn)?shù)據(jù)。
該數(shù)據(jù)在32位和64位JVM中長度都是32bit。
1.5、執(zhí)行init方法
上述過程執(zhí)行完,對象實(shí)例便已經(jīng)創(chuàng)建出來了,但是所有的成員變量(屬性字段)還都是零值。所以在new命令執(zhí)行完之后,還需要執(zhí)行
上述就是小編為大家分享的java類對象底層是如何創(chuàng)建的了,如果剛好有類似的疑惑,不妨參照上述分析進(jìn)行理解。如果想知道更多相關(guān)知識,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。