如果覺(jué)得寫(xiě)的還可以請(qǐng)關(guān)注微信公眾號(hào):程序猿的日常分享,定期更新分享直撤。
請(qǐng)解釋一下對(duì)象的創(chuàng)建過(guò)程非竿?
1、加載
2谋竖、鏈接(驗(yàn)證红柱、準(zhǔn)備、解析)
3蓖乘、初始化
4锤悄、申請(qǐng)對(duì)象內(nèi)存
5、成員變量賦默認(rèn)值
6嘉抒、調(diào)用構(gòu)造方法<init>:1)成員變量順序賦初始值 2)執(zhí)行構(gòu)造方法語(yǔ)句
對(duì)象在內(nèi)存中的存儲(chǔ)布局铁蹈?
jvm中的對(duì)象分為兩種,一種是普通對(duì)象众眨,一種是數(shù)組對(duì)象。這兩種對(duì)象在內(nèi)存中的布局是不一樣的容诬。如下圖所示:
普通對(duì)象new Object()有4部分組成娩梨,分別是對(duì)象頭、類型指針览徒、實(shí)例數(shù)據(jù)狈定、填充。
數(shù)組對(duì)象int i = new int[4]有5部分組成习蓬,分別是對(duì)象頭纽什、類型指針、數(shù)組長(zhǎng)度躲叼、實(shí)例數(shù)據(jù)芦缰、填充。
對(duì)象頭(Mark Word)
用于存儲(chǔ)對(duì)象自身的運(yùn)行時(shí)數(shù)據(jù)枫慷,如哈希碼(HashCode)让蕾、GC分代年齡、鎖狀態(tài)標(biāo)志或听、線程持有的鎖探孝、偏向線程ID、偏向時(shí)間戳等誉裆,這部分?jǐn)?shù)據(jù)的長(zhǎng)度在32位和64位的虛擬機(jī)中分別為4個(gè)字節(jié)和8個(gè)字節(jié)顿颅,官方稱它為 Mark Word。類型指針(Class Pointer)
存儲(chǔ)對(duì)象所屬類的地址足丢,就是為了標(biāo)記到底是什么類的實(shí)例粱腻。jvm默認(rèn)開(kāi)啟了指針壓縮庇配,所以占用4個(gè)字節(jié),如果關(guān)閉指針壓縮栖疑,就占用8個(gè)字節(jié)讨永。此外,指針壓縮還會(huì)影響instance data的實(shí)例對(duì)象的指針空間占用大小遇革。如果開(kāi)啟了指針壓縮卿闹,Long型的成員變量和long型的成員變量占用空間大小是有區(qū)別的:Long占用4個(gè)字節(jié);long是基礎(chǔ)類型占用8個(gè)字節(jié)萝快。如果關(guān)閉了指針壓縮:Long占用8個(gè)字節(jié)锻霎;long是基礎(chǔ)類型占用8個(gè)字節(jié)。
Hotspot開(kāi)啟內(nèi)存壓縮的規(guī)則(64位機(jī)):
1揪漩、4G以下旋恼,直接砍掉高32位
2、4G - 32G奄容,默認(rèn)開(kāi)啟內(nèi)存壓縮 ClassPointers Oops
3冰更、32G,壓縮無(wú)效昂勒,使用64位蜀细,所以內(nèi)存并不是越大越好實(shí)例數(shù)據(jù)(Instance Data)
實(shí)例數(shù)據(jù)部分是對(duì)象真正存儲(chǔ)的有效信息,也是在程序代碼中所定義各種類型的字段內(nèi)容戈盈。無(wú)論是從父類繼承下來(lái)的奠衔,還是在子類中定義的,都需要記錄下來(lái)塘娶。父類定義的變量會(huì)出現(xiàn)在子類定義的變量的前面归斤。各字段的分配策略為longs/doubles、ints刁岸、shorts/chars脏里、bytes/boolean、oops(ordinary object pointers)难捌,相同寬度的字段總是被分配到一起膝宁,便于之后取數(shù)據(jù)。填充(Padding)
填充并不是必然存在的根吁,也沒(méi)有特別的含義员淫,它僅僅起著占位符的作用。為什么需要有對(duì)齊填充呢击敌?由于JVM讀數(shù)據(jù)時(shí)是按照一塊一塊的讀取的介返,這樣讀取效率更高,64位虛擬機(jī)的話對(duì)象的大小必須是8字節(jié)的整數(shù)倍。因此圣蝎,當(dāng)對(duì)象實(shí)例數(shù)據(jù)部分沒(méi)有對(duì)齊時(shí)刃宵,就需要通過(guò)對(duì)齊填充來(lái)補(bǔ)全。數(shù)組長(zhǎng)度(Length)
存儲(chǔ)了數(shù)組對(duì)象的長(zhǎng)度徘公,占用4個(gè)字節(jié)
對(duì)象頭具體包括什么牲证?
Mark Word的結(jié)構(gòu),定義在markOop.hpp文件中关面,其中定義了32位是怎么實(shí)現(xiàn)的坦袍,64位是怎么實(shí)現(xiàn)的。源碼如下:
以64位虛擬機(jī)來(lái)看翻譯過(guò)來(lái)以后如下:
1等太、當(dāng)我們創(chuàng)建一個(gè)無(wú)鎖態(tài)對(duì)象的時(shí)候:25位沒(méi)有用捂齐;31位裝的identity Hashcode,但是只有在被調(diào)用的時(shí)候缩抡,才填充奠宜,沒(méi)有調(diào)用的時(shí)候是空的;1位沒(méi)有使用的瞻想;4位分代年齡(解釋在下面)压真;1位偏向鎖位;2位鎖標(biāo)志位蘑险。
2榴都、偏向鎖的時(shí)候:54位存下當(dāng)前線程的ID;2位存批量撤銷Epoch漠其;1位沒(méi)有使用;4位分代年齡竿音;1位偏向鎖位和屎;2位鎖標(biāo)志位。
3春瞬、 自旋鎖:62位指向線程中的Lock Record的指針柴信。Lock Record與鎖重入有關(guān),synchronize默認(rèn)是可重入的宽气。自旋鎖在競(jìng)爭(zhēng)鎖的時(shí)候随常,會(huì)在自己的內(nèi)存的線程棧中創(chuàng)建一個(gè)Lock Record對(duì)象,搶到鎖對(duì)象的資源時(shí)萄涯,鎖對(duì)象頭存的就是這個(gè)線程的Lock Record對(duì)象的指針绪氛,所以在重入的時(shí)候,會(huì)再創(chuàng)建一個(gè)Lock Record對(duì)象涝影,利用Lock Record來(lái)記錄到底瑣了多少次枣察。解鎖的時(shí)候,就將一個(gè)Lock Record移除,移除的方式是FILO序目,也就是先進(jìn)后出的原則臂痕。
4、 重量級(jí)瑣:重量級(jí)瑣是在C++代碼層面進(jìn)行的猿涨,會(huì)生成一個(gè)ObjectMonitor對(duì)象握童,這個(gè)對(duì)象中記錄了一系列的隊(duì)列。
5叛赚、分代年齡:JVM有10種垃圾回收器澡绩,前面7種都涉及分代年齡,采用分代算法红伦。當(dāng)我們創(chuàng)建一個(gè)對(duì)象的時(shí)候英古,把它放在年輕代中,每經(jīng)過(guò)一次垃圾回收后年齡就+1昙读,也就是垃圾回收無(wú)法回收掉這個(gè)對(duì)象召调,它的年齡就會(huì)不斷的增長(zhǎng),到達(dá)15蛮浑,因?yàn)?個(gè)字節(jié)最大為15唠叛,就轉(zhuǎn)到老齡代,年輕代的回收就不再對(duì)它進(jìn)行回收沮稚。
6艺沼、hashCode部分:對(duì)象頭上的hashCode并不是我們調(diào)用重寫(xiě)的hashCode()方法生成的,而是為重寫(xiě)的hashCode()方法或者調(diào)用System.identityHashcode()方法才能獲取并且存入對(duì)象頭中蕴掏。通俗來(lái)講障般,這里的hashCode是按照原始內(nèi)容計(jì)算的,重寫(xiě)過(guò)的hashCode()方法計(jì)算的結(jié)果并不會(huì)存在此處盛杰。如果對(duì)象沒(méi)有重寫(xiě)hashCode()方法挽荡,那么默認(rèn)調(diào)用的os::random產(chǎn)生hashCode,也可以通過(guò)System.identityHashcode()獲取即供。os::random產(chǎn)生hashCode的規(guī)則是:next_rand = (16807seed)mod(2*31-1)定拟,因此可以使用31位存儲(chǔ)空間進(jìn)行存儲(chǔ),并且一旦產(chǎn)生這個(gè)hashCode逗嫡,JVM就會(huì)記錄在mark word中青自。
關(guān)于鎖有幾個(gè)需要注意的地方:
1、當(dāng)一個(gè)對(duì)象已經(jīng)計(jì)算過(guò)identity hash code驱证,則它就無(wú)法進(jìn)入偏向鎖狀態(tài)延窜。因?yàn)槿绻呀?jīng)計(jì)算過(guò)identity hash code的值以后,在上圖中偏向鎖記錄線程ID的內(nèi)存已經(jīng)被占用了抹锄。
2需曾、當(dāng)一個(gè)對(duì)象正處于偏向鎖狀態(tài),并且需要計(jì)算identity hash code的話,則它的偏向鎖會(huì)被撤銷呆万、膨脹為重量級(jí)鎖
3商源、重量級(jí)鎖的實(shí)現(xiàn)中,ObjectMonitor類里有字段可以記錄非加鎖狀態(tài)下的mark word谋减,其中可以存儲(chǔ)identity hash code的值牡彻。
對(duì)象怎么定位
JVM中對(duì)象訪問(wèn)定位兩種方式:
1、直接指針訪問(wèn):Java棧直接與對(duì)象進(jìn)行訪問(wèn)出爹,在Java堆中對(duì)象帆布中必須考慮存儲(chǔ)訪問(wèn)類型的數(shù)據(jù)的相關(guān)信息 庄吼,直接指針訪問(wèn)的優(yōu)點(diǎn)比較明顯,就是訪問(wèn)速度快严就,不需要和句柄一樣指針定位的開(kāi)銷 总寻。缺點(diǎn)也比較明顯,就是對(duì)象在GC過(guò)程中梢为,在新生代區(qū)域復(fù)制移動(dòng)時(shí)渐行,會(huì)比較麻煩。如下圖:
2铸董、通過(guò)句柄池方式訪問(wèn):在Java堆中分出一塊內(nèi)存進(jìn)行存儲(chǔ)句柄池祟印,在棧中存儲(chǔ)的是句柄的地址,通過(guò)句柄池訪問(wèn)有獨(dú)特的優(yōu)點(diǎn)粟害,就是當(dāng)對(duì)象移動(dòng)的時(shí)候(垃圾回收的時(shí)候移動(dòng)很普遍)蕴忆,這樣值需要改變句柄中的指針,但是棧中的指針不需要變化悲幅,因?yàn)闂V写鎯?chǔ)的是句柄的地址套鹅。那么對(duì)應(yīng)的缺點(diǎn)就是需要兩次指針轉(zhuǎn)換進(jìn)行訪問(wèn),訪問(wèn)速度比直接指針訪問(wèn)稍慢一些汰具。如下圖:
對(duì)象怎么分配
對(duì)象分配流程如下圖:
1芋哭、當(dāng)我們new出一個(gè)對(duì)象,JVM會(huì)首先嘗試往棧上分配郁副,如果能夠分配得下,就分配到棧上分配到棧上的對(duì)象有好處就是不需要GC進(jìn)行管理豌习,什么時(shí)候不需要用到此對(duì)象了存谎,將對(duì)象出棧就可以了。但是分配到棧上的對(duì)象是有要求的:第一肥隆,對(duì)象比較小既荚,因?yàn)闂?臻g本來(lái)就不夠大栋艳;第二恰聘,對(duì)象比較簡(jiǎn)單。
2、如果棧上分配不下晴叨,我們就判斷這個(gè)對(duì)象是不是夠大凿宾,如果足夠大就直接放在老年代區(qū),在老年代區(qū)的對(duì)象經(jīng)過(guò)一次全量垃圾回收FGC后兼蕊,才有可能被回收掉初厚。
3、如果如果棧上分配不下并且對(duì)象不大孙技,就會(huì)判斷對(duì)象能否被存在線程本地分配緩沖區(qū)-TLAB(Thread Local Allocation Buffer)产禾。但是不管放不放得下,都是放在新生代區(qū)的伊甸區(qū)eden牵啦。 但是因?yàn)槎咽枪蚕淼难乔椋鄠€(gè)線程可以同時(shí)創(chuàng)建對(duì)象就可能會(huì)爭(zhēng)奪同一塊內(nèi)存區(qū)域,所以為了保證線程安全哈雏,Eden區(qū)又被分配成一個(gè)個(gè)線程本地分配緩沖區(qū)楞件,這個(gè)TLAB是線程私有的,每個(gè)線程都有自己的TLAB僧著,避免了多線程環(huán)境下使用同步技術(shù)帶來(lái)的性能損耗履因。
4、伊甸區(qū)eden的對(duì)象在經(jīng)過(guò)一次GC后盹愚,如果被回收掉了栅迄,那就結(jié)束了生命周期。
5皆怕、伊甸區(qū)eden的對(duì)象在經(jīng)過(guò)一次GC后毅舆,如果沒(méi)有被回收掉,會(huì)被拷貝到幸存者區(qū)survivor1愈腾,對(duì)比上面的堆內(nèi)存邏輯分區(qū)圖憋活。幸存者區(qū)survivor1中的對(duì)象再經(jīng)過(guò)一次GC后如果對(duì)象還存活,那么就拷貝到幸存者區(qū)survivor2并且清理掉幸存者區(qū)survivor1中的所有對(duì)象虱黄,再有GC就反復(fù)這個(gè)操作悦即,直到對(duì)象的分代年齡達(dá)到了移到老年代的界限(一般默認(rèn)是15),就會(huì)被移到老年代中橱乱。
Object o = new Object()在內(nèi)存中占用多少字節(jié)辜梳?
這里考察的知識(shí)點(diǎn)是對(duì)象在內(nèi)存中的存儲(chǔ)布局結(jié)構(gòu)和類指針以及普通對(duì)象指針的概念。存儲(chǔ)布局不再多說(shuō)泳叠,類指針就是存儲(chǔ)布局中的class pointer作瞄,普通對(duì)象指針就是存儲(chǔ)布局中的instance data中,成員變量如果不是基礎(chǔ)類型而是引用類型危纫,那么也會(huì)有普通對(duì)象指針指向所屬類宗挥。默認(rèn)情況下JVM是開(kāi)啟了類指針和普通對(duì)象指針的指針壓縮乌庶,將8個(gè)字節(jié)壓縮成了4個(gè)字節(jié)。我們用代碼輸出來(lái)觀察對(duì)象的大小契耿,實(shí)驗(yàn)代碼如下:
(1) 默認(rèn)開(kāi)啟所有指針壓縮的情況下輸出如下:
(2) 關(guān)閉類指針壓縮后瞒大,如下:
如果覺(jué)得寫(xiě)的還可以請(qǐng)關(guān)注微信公眾號(hào):程序猿的日常分享,定期更新分享宵喂。