一個(gè)對(duì)象占多少字節(jié)逗宜?
關(guān)于對(duì)象的大小,對(duì)于C/C++來說空骚,都是有sizeof函數(shù)可以直接獲取的纺讲,但是Java似乎沒有這樣的方法。不過還好囤屹,在JDK1.5之后引入了Instrumentation類熬甚,這個(gè)類提供了計(jì)算對(duì)象內(nèi)存占用量的方法。至于具體Instrumentation類怎么用就不說了肋坚,可以參看這篇文章如何精確地測(cè)量java對(duì)象的大小乡括。
不過有一點(diǎn)不同的是,這篇文章使用命令行傳入JVM參數(shù)來指定代理智厌,這里我通過Eclipse設(shè)置JVM參數(shù):
了解更多Java知識(shí)诲泌,獲取原視頻,源碼铣鹏,學(xué)習(xí)交流敷扫,那就加入小編的學(xué)習(xí)交流群吧!616 959 444
后面的是我打的agent.jar的具體路徑诚卸。剩下的就不說了葵第,看一下測(cè)試代碼:
1 public class JVMSizeofTest { 2 3 @Test 4 public void testSize() { 5 System.out.println("Object對(duì)象的大小:" + JVMSizeof.sizeOf(new Object()) + "字節(jié)"); 6 System.out.println("字符a的大泻夏纭:" + JVMSizeof.sizeOf('a') + "字節(jié)"); 7 System.out.println("整型1的大懈摇:" + JVMSizeof.sizeOf(new Integer(1)) + "字節(jié)"); 8 System.out.println("字符串a(chǎn)aaaa的大小:" + JVMSizeof.sizeOf(new String("aaaaa")) + "字節(jié)"); 9 System.out.println("char型數(shù)組(長(zhǎng)度為1)的大斜栌洹:" + JVMSizeof.sizeOf(new char[1]) + "字節(jié)");10 }11 12 }
運(yùn)行結(jié)果為:
Object對(duì)象的大姓な堋:16字節(jié)
接著,代碼不變恭朗,加入一條虛擬機(jī)參數(shù)"-XX:-UseCompressedOops"屏镊,再運(yùn)行一遍測(cè)試類,運(yùn)行結(jié)果為:
Object對(duì)象的大刑等:16字節(jié)
后文來詳細(xì)解釋一下原因而芥。
Java對(duì)象大小計(jì)算方法
JVM對(duì)于普通對(duì)象和數(shù)組對(duì)象的大小計(jì)算方式有所不同,我畫了一張圖說明:
了解更多Java知識(shí)膀值,獲取原視頻棍丐,源碼误辑,學(xué)習(xí)交流,那就加入小編的學(xué)習(xí)交流群吧歌逢!616 959 444
解釋一下其中每個(gè)部分:
Mark Word:存儲(chǔ)對(duì)象運(yùn)行時(shí)記錄信息巾钉,占用內(nèi)存大小與機(jī)器位數(shù)一樣,即32位機(jī)占4字節(jié)秘案,64位機(jī)占8字節(jié)
元數(shù)據(jù)指針:指向描述類型的Klass對(duì)象(Java類的C++對(duì)等體)的指針砰苍,Klass對(duì)象包含了實(shí)例對(duì)象所屬類型的元數(shù)據(jù),因此該字段被稱為元數(shù)據(jù)指針阱高,JVM在運(yùn)行時(shí)將頻繁使用這個(gè)指針定位到位于方法區(qū)內(nèi)的類型信息赚导。這個(gè)數(shù)據(jù)的大小稍后說
數(shù)組長(zhǎng)度:數(shù)組對(duì)象特有,一個(gè)指向int型的引用類型赤惊,用于描述數(shù)組長(zhǎng)度吼旧,這個(gè)數(shù)據(jù)的大小和元數(shù)據(jù)指針大小相同,同樣稍后說
實(shí)例數(shù)據(jù):實(shí)例數(shù)據(jù)就是8大基本數(shù)據(jù)類型byte未舟、short黍少、int、long处面、float厂置、double、char魂角、boolean(對(duì)象類型也是由這8大基本數(shù)據(jù)類型復(fù)合而成)昵济,每種數(shù)據(jù)類型占多少字節(jié)就不一一例舉了
填充:不定,HotSpot的對(duì)齊方式為8字節(jié)對(duì)齊野揪,即一個(gè)對(duì)象必須為8字節(jié)的整數(shù)倍访忿,因此如果最后前面的數(shù)據(jù)大小為17則填充7,前面的數(shù)據(jù)大小為18則填充6斯稳,以此類推
最后再說說元數(shù)據(jù)指針的大小海铆。元數(shù)據(jù)指針是一個(gè)引用類型,因此正常來說64位機(jī)元數(shù)據(jù)指針應(yīng)當(dāng)為8字節(jié)挣惰,32位機(jī)元數(shù)據(jù)指針應(yīng)當(dāng)為4字節(jié)卧斟,但是HotSpot中有一項(xiàng)優(yōu)化是對(duì)元數(shù)據(jù)類型指針進(jìn)行壓縮存儲(chǔ),使用JVM參數(shù):
-XX:+UseCompressedOops開啟壓縮
-XX:-UseCompressedOops關(guān)閉壓縮
HotSpot默認(rèn)是前者憎茂,即開啟元數(shù)據(jù)指針壓縮珍语,當(dāng)開啟壓縮的時(shí)候,64位機(jī)上的元數(shù)據(jù)指針將占據(jù)4個(gè)字節(jié)的大小竖幔。換句話說就是當(dāng)開啟壓縮的時(shí)候板乙,64位機(jī)上的引用將占據(jù)4個(gè)字節(jié),否則是正常的8字節(jié)拳氢。
Java對(duì)象內(nèi)存大小計(jì)算
有了上面的理論基礎(chǔ)募逞,我們就可以分析JVMSizeofTest類的執(zhí)行結(jié)果及為什么加入了"-XX:-UseCompressedOops"這條參數(shù)后同一個(gè)對(duì)象的大小會(huì)有差異了蛋铆。
首先是Object對(duì)象的大小:
開啟指針壓縮時(shí)放接,8字節(jié)Mark Word + 4字節(jié)元數(shù)據(jù)指針 = 12字節(jié)刺啦,由于12字節(jié)不是8的倍數(shù),因此填充4字節(jié)透乾,對(duì)象Object占據(jù)16字節(jié)內(nèi)存
關(guān)閉指針壓縮時(shí),8字節(jié)Mark Word + 8字節(jié)元數(shù)據(jù)指針 = 16字節(jié)磕秤,由于16字節(jié)正好是8的倍數(shù)乳乌,因此不需要填充字節(jié),對(duì)象Object占據(jù)16字節(jié)內(nèi)存
接著是字符'a'的大惺信亍:
開啟指針壓縮時(shí)汉操,8字節(jié)Mark Word + 4字節(jié)元數(shù)據(jù)指針 + 1字節(jié)char = 13字節(jié),由于13字節(jié)不是8的倍數(shù)蒙兰,因此填充3字節(jié)磷瘤,字符'a'占據(jù)16字節(jié)內(nèi)存
關(guān)閉指針壓縮時(shí),8字節(jié)Mark Word + 8字節(jié)元數(shù)據(jù)指針 + 1字節(jié)char = 17字節(jié)搜变,由于17字節(jié)不是8的倍數(shù)采缚,因此填充7字節(jié),字符'a'占據(jù)24字節(jié)內(nèi)存
了解更多Java知識(shí)挠他,獲取原視頻扳抽,源碼,學(xué)習(xí)交流殖侵,那就加入小編的學(xué)習(xí)交流群吧贸呢!616 959 444
接著是整型1的大小:
開啟指針壓縮時(shí)拢军,8字節(jié)Mark Word + 4字節(jié)元數(shù)據(jù)指針 + 4字節(jié)int = 16字節(jié)楞陷,由于16字節(jié)正好是8的倍數(shù),因此不需要填充字節(jié)茉唉,整型1占據(jù)16字節(jié)內(nèi)存
關(guān)閉指針壓縮時(shí)固蛾,8字節(jié)Mark Word + 8字節(jié)元數(shù)據(jù)指針 + 4字節(jié)int = 20字節(jié),由于20字節(jié)正好是8的倍數(shù)度陆,因此填充4字節(jié)魏铅,整型1占據(jù)24字節(jié)內(nèi)存
接著是字符串"aaaaa"的大小,所有靜態(tài)字段不需要管坚芜,只關(guān)注實(shí)例字段览芳,String對(duì)象中實(shí)例字段有"char value[]"與"int hash",由此可知:
開啟指針壓縮時(shí)鸿竖,8字節(jié)Mark Word + 4字節(jié)元數(shù)據(jù)指針 + 4字節(jié)引用 + 4字節(jié)int = 20字節(jié)沧竟,由于20字節(jié)不是8的倍數(shù)铸敏,因此填充4字節(jié),字符串"aaaaa"占據(jù)24字節(jié)內(nèi)存
關(guān)閉指針壓縮時(shí)悟泵,8字節(jié)Mark Word + 8字節(jié)元數(shù)據(jù)指針 + 8字節(jié)引用 + 4字節(jié)int = 28字節(jié)杈笔,由于28字節(jié)不是8的倍數(shù),因此填充4字節(jié)糕非,字符串"aaaaa"占據(jù)32字節(jié)內(nèi)存
最后是長(zhǎng)度為1的char型數(shù)組的大忻删摺:
開啟指針壓縮時(shí)土榴,8字節(jié)的Mark Word + 4字節(jié)的元數(shù)據(jù)指針 + 4字節(jié)的數(shù)組大小引用 + 1字節(jié)char = 17字節(jié)片效,由于17字節(jié)不是8的倍數(shù),因此填充7字節(jié)投储,長(zhǎng)度為1的char型數(shù)組占據(jù)24字節(jié)內(nèi)存
關(guān)閉指針壓縮時(shí)衡招,8字節(jié)的Mark Word + 8字節(jié)的元數(shù)據(jù)指針 + 8字節(jié)的數(shù)組大小引用 + 1字節(jié)char = 25字節(jié)篱昔,由于25字節(jié)不是8的倍數(shù),因此填充7字節(jié)始腾,長(zhǎng)度為1的char型數(shù)組占據(jù)32字節(jié)內(nèi)存
Mark Word
Mark Word前面已經(jīng)看到過了州刽,它是Java對(duì)象頭中很重要的一部分。Mark Word存儲(chǔ)的是對(duì)象自身的運(yùn)行數(shù)據(jù)浪箭,如哈希碼(HashCode)穗椅、GC分代年齡、鎖狀態(tài)標(biāo)識(shí)奶栖、線程持有的鎖房待、偏向線程ID、偏向時(shí)間戳等等驼抹。
不過由于對(duì)象需要存儲(chǔ)的運(yùn)行時(shí)數(shù)據(jù)很多桑孩,其實(shí)已經(jīng)超出了32位、64位Bitmap結(jié)構(gòu)所能記錄的限度框冀,但是對(duì)象頭是與對(duì)象自身定義的數(shù)據(jù)無(wú)關(guān)的額外存儲(chǔ)成本流椒,考慮到虛擬機(jī)的空間效率,Mark Word被設(shè)計(jì)成一個(gè)非固定的數(shù)據(jù)結(jié)構(gòu)以便在極小的空間內(nèi)存儲(chǔ)盡量多的信息明也。例如在32位的HotSpot虛擬機(jī)中對(duì)象未被鎖定的狀態(tài)下宣虾,Mark Word的32個(gè)Bits空間中的25Bits用于存儲(chǔ)對(duì)象哈希碼(HashCode),4Bits用于存儲(chǔ)對(duì)象分代年齡温数,2Bits用于存儲(chǔ)鎖標(biāo)識(shí)位绣硝,1Bit固定位0。在其他狀態(tài)(輕量級(jí)鎖定撑刺、重量級(jí)鎖定鹉胖、GC標(biāo)記、可偏向)下對(duì)象的存儲(chǔ)內(nèi)容如下圖所示:
這里要特別關(guān)注的是鎖狀態(tài),后文將對(duì)鎖狀態(tài)及鎖狀態(tài)的變化進(jìn)行研究甫菠。
鎖的升級(jí)
如上圖所示挠铲,鎖的狀態(tài)共有四種:無(wú)鎖態(tài)、偏向鎖寂诱、輕量級(jí)鎖和重量級(jí)鎖拂苹,其中偏向鎖和輕量級(jí)鎖是JDK1.6開始為了減少獲得鎖和釋放鎖帶來的性能消耗而引入的。
四種鎖的狀態(tài)會(huì)隨著競(jìng)爭(zhēng)情況逐漸升級(jí)痰洒,鎖可以升級(jí)但是不能降級(jí)瓢棒,意味著偏向鎖可以升級(jí)為輕量級(jí)鎖但是輕量級(jí)鎖不能降級(jí)為偏向鎖,目的是為了提高獲得鎖和釋放鎖的效率丘喻。用一張圖表示這種關(guān)系:
偏向鎖
HotSpot作者經(jīng)過以往的研究發(fā)現(xiàn)大多數(shù)情況下鎖不僅不存在多線程競(jìng)爭(zhēng)脯宿,而且總是由同一線程多次獲得,為了讓線程獲得鎖的代碼更低因此引入了偏向鎖。偏向鎖的獲取過程為:
訪問Mark Word中偏向鎖的標(biāo)識(shí)是否設(shè)置為1仓犬,所標(biāo)志位是否為01----確認(rèn)為可偏向狀態(tài)
如果為可偏向狀態(tài)嗅绰,則測(cè)試線程id是否指向當(dāng)前線程舍肠,如果是搀继,執(zhí)行(5),否則執(zhí)行(3)
如果線程id并為指向當(dāng)前線程翠语,通過CAS操作競(jìng)爭(zhēng)鎖叽躯。如果競(jìng)爭(zhēng)成功,則將Mark Word中的線程id設(shè)置為當(dāng)前線程id肌括,然后執(zhí)行(5)点骑;如果競(jìng)爭(zhēng)失敗,執(zhí)行(4)
如果CAS獲取偏向鎖失敗谍夭,則表示有競(jìng)爭(zhēng)黑滴。當(dāng)達(dá)到全局安全點(diǎn)(safepoint)時(shí)獲得偏向鎖的線程被掛起,偏向鎖升級(jí)為輕量級(jí)鎖(因?yàn)槠蜴i是假設(shè)沒有競(jìng)爭(zhēng)紧索,但是這里出現(xiàn)了競(jìng)爭(zhēng)袁辈,要對(duì)偏向鎖進(jìn)行升級(jí)),然后被阻塞在安全點(diǎn)的線程繼續(xù)往下執(zhí)行同步代碼
執(zhí)行同步代碼
有獲取就有釋放珠漂,偏向鎖的釋放點(diǎn)在于上述的第(4)步晚缩,只有遇到其他線程嘗試競(jìng)爭(zhēng)偏向鎖時(shí),持有偏向鎖的線程才會(huì)釋放鎖媳危,線程不會(huì)主動(dòng)去釋放偏向鎖荞彼。偏向鎖的釋放過程為:
需要等待全局安全點(diǎn)(在這個(gè)時(shí)間點(diǎn)上沒有字節(jié)碼正在執(zhí)行)
它會(huì)首先暫停擁有偏向鎖的線程,判斷鎖對(duì)象是否處于被鎖定狀態(tài)
偏向鎖釋放后恢復(fù)到未鎖定(標(biāo)識(shí)位為01)或輕量級(jí)鎖(標(biāo)識(shí)位為00)狀態(tài)
輕量級(jí)鎖
輕量級(jí)鎖的加鎖過程為:
在代碼進(jìn)入同步塊的時(shí)候待笑,如果同步對(duì)象鎖狀態(tài)為無(wú)鎖狀態(tài)鸣皂,JVM首先將在當(dāng)前線程的棧幀中建立一個(gè)名為鎖記錄(Lock Record)的空間,用于存儲(chǔ)鎖對(duì)象目前的Mark Word的拷貝,官方稱之為Displaced Mark Word签夭,此時(shí)線程堆棧與對(duì)象頭的狀態(tài)如圖所示
拷貝對(duì)象頭中的Mark Word復(fù)制到鎖記錄中
拷貝成功后齐邦,JVM將使用CAS操作嘗試將對(duì)象的Mark Word更新為指向Lock Record的指針,并將Lock Record里的owner指針指向Object Mark Word第租,如果更新成功措拇,則執(zhí)行步驟(4),否則執(zhí)行步驟(5)
如果更新動(dòng)作成功慎宾,那么當(dāng)前線程就擁有了該對(duì)象的鎖丐吓,并且對(duì)象Mark Word的鎖標(biāo)識(shí)位設(shè)置為00,即表示此對(duì)象處于輕量級(jí)鎖狀態(tài)趟据,此時(shí)線堆棧與對(duì)象頭的狀態(tài)如圖所示
如果更新動(dòng)作失敗券犁,JVM首先會(huì)檢查對(duì)象的Mark Word是否指向當(dāng)前線程的棧幀,如果是就說明當(dāng)前線程已經(jīng)擁有了這個(gè)對(duì)象的鎖汹碱,那就可以直接進(jìn)入同步塊繼續(xù)執(zhí)行粘衬。否則說明多個(gè)線程競(jìng)爭(zhēng)鎖,輕量級(jí)鎖就要膨脹為重量級(jí)鎖咳促,鎖標(biāo)識(shí)的狀態(tài)值變?yōu)?0稚新,Mark Word中存儲(chǔ)的就是指向重量級(jí)鎖的指針,后面等待鎖的線程也要進(jìn)入阻塞狀態(tài)跪腹。而當(dāng)前線程變嘗試使用自旋來獲取鎖褂删,自旋就是為了不讓線程阻塞,而采用循環(huán)去獲取鎖的過程
偏向鎖冲茸、輕量級(jí)鎖與重量級(jí)鎖的對(duì)比
下面用一張表格來對(duì)比一下偏向鎖屯阀、輕量級(jí)鎖與重量級(jí)鎖,網(wǎng)上看到的轴术,我覺得寫得非常好难衰,為了加深記憶我自己又手打了一遍:
了解更多Java知識(shí),獲取原視頻逗栽,源碼盖袭,學(xué)習(xí)交流,那就加入小編的學(xué)習(xí)交流群吧祭陷!616 959 444
==================================================================================
我不能保證寫的每個(gè)地方都是對(duì)的苍凛,但是至少能保證不復(fù)制、不黏貼兵志,保證每一句話醇蝴、每一行代碼都經(jīng)過了認(rèn)真的推敲、仔細(xì)的斟酌想罕。每一篇文章的背后悠栓,希望都能看到自己對(duì)于技術(shù)霉涨、對(duì)于生活的態(tài)度。
我相信喬布斯說的惭适,只有那些瘋狂到認(rèn)為自己可以改變世界的人才能真正地改變世界笙瑟。面對(duì)壓力,我可以挑燈夜戰(zhàn)癞志、不眠不休往枷;面對(duì)困難,我愿意迎難而上凄杯、永不退縮错洁。
其實(shí)我想說的是,我只是一個(gè)程序員戒突,這就是我現(xiàn)在純粹人生的全部屯碴。