動(dòng)手觀(guān)察java對(duì)象內(nèi)存大?。ɑ?4位操作系統(tǒng))
引言
hello,大家好,我是小面!今天有個(gè)小伙伴私信我說(shuō)怎么能親眼看見(jiàn)java對(duì)象占用的大小呢?那小面就這個(gè)問(wèn)題做一個(gè)簡(jiǎn)單的實(shí)驗(yàn)來(lái),基于64位操作系統(tǒng)來(lái)看看對(duì)象的大小。
在開(kāi)始實(shí)驗(yàn)之前,也有一些老生常談的知識(shí)需要鋪墊一下。
查看虛擬機(jī)配置
通過(guò)-xx參數(shù) PrintCommandLineFlags查看

那么我們看到了一堆輸出參數(shù),參數(shù)前面帶有-XX,-XX是指什么呢?
這個(gè)是JVM為我們提供了三種參數(shù)方式(標(biāo)準(zhǔn)選項(xiàng)、X選項(xiàng)、XX選項(xiàng))
標(biāo)準(zhǔn)選項(xiàng)
這類(lèi)選項(xiàng)功能是非常穩(wěn)定的,在后續(xù)jdk版本中也不太會(huì)發(fā)生變化。
在cmd終端運(yùn)行java、 java -help 就可以看到所有的標(biāo)準(zhǔn)選項(xiàng)。
所有的標(biāo)準(zhǔn)選項(xiàng)都是以 - 開(kāi)頭,例如-version,-server等。
X選項(xiàng)
這類(lèi)選項(xiàng)功能還是很穩(wěn)定,后續(xù)版本中可能會(huì)改變,也有可能不再提供了。
運(yùn)行 java -X 命令可以看到所有的X選項(xiàng)。
這類(lèi)選項(xiàng)都是以 -X 開(kāi)頭,比如 -Xmx -Xms -Xmn -Xss。
XX選項(xiàng)
這類(lèi)選項(xiàng)是屬于實(shí)驗(yàn)性,主要是給JVM開(kāi)發(fā)者用于開(kāi)發(fā)和調(diào)試JVM的,后續(xù)的版本中有可能會(huì)變化。
如果是布爾類(lèi)型的選項(xiàng),它的格式為-XX:+flag或者-XX:-flag,分別表示開(kāi)啟和關(guān)閉該選項(xiàng)。
針對(duì)非布爾類(lèi)型的選項(xiàng),它的格式為-XX:flag=value
例如:
-XX:InitialHeapSize=132558400 #JVM初始堆內(nèi)存-XX:MaxHeapSize=2120934400 #JVM最大堆內(nèi)存-XX:+PrintCommandLineFlags-XX:+UseCompressedClassPointers-XX:+UseCompressedOops-XX:-UseLargePagesIndividualAllocation-XX:+UseParallelGC
其中有一項(xiàng)是UseCompressedClassPointers,這個(gè)叫開(kāi)啟指針壓縮
在HotSpot 虛擬機(jī)中,對(duì)象在內(nèi)存中布局分為三塊區(qū)域,對(duì)象頭(Header)、實(shí)例數(shù)據(jù)(Instance Data)和對(duì)齊填充(Padding),普通對(duì)象和數(shù)組對(duì)象又稍有差別,我們一起來(lái)看一看。
普通對(duì)象
對(duì)象頭:markword 8個(gè)字節(jié)
ClassPointer指針:-XX:+UseCompressedClassPointers 大小為4字節(jié) -XX:-UseCompressedClassPointers不開(kāi)啟為8字節(jié)。表示是否啟用類(lèi)指針壓縮,因?yàn)閷?duì)于任何一個(gè)jvm中的對(duì)象而言,其內(nèi)部都有一個(gè)指向自己對(duì)應(yīng)類(lèi)(屬于哪個(gè)class)的指針(Java習(xí)慣叫引用),在64位的Java虛擬機(jī)中,默認(rèn)是啟動(dòng)壓縮的
實(shí)例數(shù)據(jù)
引用類(lèi)型:-XX:+UseCompressedOops 大小為4字節(jié) -XX:-UseCompressedOops不開(kāi)啟為8字節(jié)
表示是否使用普通對(duì)象指針壓縮,Oops是Ordinary object pointers的縮寫(xiě),就是任何指向一個(gè)在堆中的對(duì)象(非簡(jiǎn)單類(lèi)型)的指針,默認(rèn)也是啟動(dòng)壓縮的Padding對(duì)齊,8的倍數(shù)(加上這個(gè)對(duì)齊的字節(jié)就是整個(gè)對(duì)象的大小,大小是8的倍數(shù))
數(shù)組對(duì)象
對(duì)象頭:markword 8個(gè)字節(jié)
ClassPointer指針同上
數(shù)組長(zhǎng)度:4字節(jié)
數(shù)組數(shù)據(jù)
對(duì)齊 8的倍數(shù)(加上這個(gè)對(duì)齊的字節(jié)就是整個(gè)對(duì)象的大小,大小是8的倍數(shù))
可以看見(jiàn),數(shù)組對(duì)象比普通對(duì)象多了一個(gè)數(shù)組長(zhǎng)度對(duì)象,另外普通對(duì)象和數(shù)組對(duì)象都是有對(duì)象頭的,我們來(lái)看下對(duì)象頭。
對(duì)象頭
對(duì)象頭主要包括Mark Word,對(duì)象指針,數(shù)組長(zhǎng)度
Mark Word

Mark Word占用8字節(jié)空間,其主要用于鎖升級(jí)。
? ?無(wú)鎖:Mark Word保存對(duì)象HashCode,鎖標(biāo)志位是01,是否偏向鎖為0。
? ?偏向鎖:請(qǐng)求進(jìn)來(lái)先檢查是否包括線(xiàn)程id,沒(méi)有的話(huà)保存線(xiàn)程id,修改是否偏向鎖標(biāo)識(shí)。如果包括線(xiàn)程id,則判斷線(xiàn)程id是否是本線(xiàn)程id,是的話(huà)繼續(xù)向下執(zhí)行,不是的則進(jìn)行搶鎖操作,搶鎖成功更改線(xiàn)程id,失敗則升級(jí)為輕量級(jí)鎖。
? ?輕量級(jí)鎖:偏向鎖搶奪失敗后升級(jí)為輕量級(jí)鎖,jvm通過(guò)cas操作在當(dāng)前線(xiàn)程的線(xiàn)程棧中開(kāi)辟一塊單獨(dú)的空間保存指向?qū)ο箧iMark Word的指針,并且在對(duì)象鎖Mark Word中保存指向這片空間的指針,如果保存成功,則表示當(dāng)前線(xiàn)程搶到鎖,繼續(xù)執(zhí)行。保存失敗則表示搶鎖失敗。
? ?輕量級(jí)鎖搶鎖失敗JVM使用自旋鎖不斷重試搶鎖避免線(xiàn)程阻塞,當(dāng)達(dá)到一定次數(shù)后(jdk1.7后JVM控制自旋次數(shù)),升級(jí)為重量級(jí)鎖。
? -XX:-UseSpinning ?控制是否開(kāi)啟自旋鎖,默認(rèn)開(kāi)啟
? ?重量級(jí)鎖: 自旋鎖自旋一定次數(shù)還沒(méi)獲得鎖則升級(jí)為重量級(jí)鎖,此時(shí)只有獲取到鎖的線(xiàn)程能執(zhí)行,其余線(xiàn)程阻塞。
對(duì)于對(duì)象有了一個(gè)初步的認(rèn)識(shí)后,小面將通過(guò)一個(gè)小實(shí)驗(yàn)來(lái)觀(guān)測(cè)對(duì)象的大小,讓大家有一個(gè)實(shí)質(zhì)性的感受。
實(shí)驗(yàn)(觀(guān)察對(duì)象的大?。?/h1>新建項(xiàng)目(以IDEA為例)ObjectSize (1.8)一個(gè)單獨(dú)的項(xiàng)目


創(chuàng)建文件ObjectSizeAgent
新建項(xiàng)目(以IDEA為例)ObjectSize (1.8)一個(gè)單獨(dú)的項(xiàng)目


創(chuàng)建文件ObjectSizeAgent
import java.lang.instrument.Instrumentation;public class ObjectSizeAgent { ? ?private static Instrumentation inst; ? ?public static void premain(String agentArgs, Instrumentation _inst) {
? ? ? ?inst = _inst;
? ?} ? ?public static long sizeOf(Object o) { ? ? ? ?return inst.getObjectSize(o);
? ?}
}

src目錄下創(chuàng)建META-INF/MANIFEST.MF
Manifest-Version: 1.0Created-By: lnf.com Premain-Class: com.lnf.ObjectSizeAgent

注意Premain-Class這行必須是新的一行(回車(chē) + 換行),確認(rèn)idea不能有任何錯(cuò)誤提示
pom打包設(shè)置:
<build>
? ? ? ?<plugins>
? ? ? ? ? ?<plugin>
? ? ? ? ? ? ? ?<groupId>org.apache.maven.plugins</groupId>
? ? ? ? ? ? ? ?<artifactId>maven-jar-plugin</artifactId>
? ? ? ? ? ? ? ?<version>3.1.0</version>
? ? ? ? ? ? ? ?<configuration>
? ? ? ? ? ? ? ? ? ?<archive>
? ? ? ? ? ? ? ? ? ? ? ?<manifestEntries>
? ? ? ? ? ? ? ? ? ? ? ? ? ?<Premain-Class>com.lnf.ObjectSizeAgent</Premain-Class>
? ? ? ? ? ? ? ? ? ? ? ? ? ?<Agent-Class>com.lnf.ObjectSizeAgent</Agent-Class>
? ? ? ? ? ? ? ? ? ? ? ? ? ?<Can-Redefine-Classes>true</Can-Redefine-Classes>
? ? ? ? ? ? ? ? ? ? ? ? ? ?<Can-Retransform-Classes>true</Can-Retransform-Classes>
? ? ? ? ? ? ? ? ? ? ? ?</manifestEntries>
? ? ? ? ? ? ? ? ? ?</archive>
? ? ? ? ? ? ? ?</configuration>
? ? ? ? ? ?</plugin>
? ? ? ?</plugins>
? ?</build>

打包jar文件

創(chuàng)建測(cè)量類(lèi):
import com.lnf.jvm.agent.ObjectSizeAgent; ? public class T03_SizeOfAnObject { ? ? ? public static void main(String[] args) {
? ? ? ? ? System.out.println(ObjectSizeAgent.sizeOf(new Object()));
? ? ? ? ? System.out.println(ObjectSizeAgent.sizeOf(new int[] {}));
? ? ? ? ? System.out.println(ObjectSizeAgent.sizeOf(new P()));
? ? ? } ? ? ? private static class P { ? ? ? ? ? ? ? ? ? ? ? ? ? //8 _markword
? ? ? ? ? ? ? ? ? ? ? ? ? //4 _oop指針
? ? ? ? ? int id; ? ? ? ? //4
? ? ? ? ? String name; ? ?//4
? ? ? ? ? int age; ? ? ? ?//4
? ? ? ? ? byte b1; ? ? ? ?//1
? ? ? ? ? byte b2; ? ? ? ?//1
? ? ? ? ? Object o; ? ? ? //4
? ? ? ? ? byte b3; ? ? ? ?//1
? ? ? }
? }
在需要使用該Agent Jar的項(xiàng)目中引入該Jar包
project structure - project settings - library 添加該jar包

7. 運(yùn)行時(shí)需要該Agent Jar的類(lèi),加入?yún)?shù):
-javaagent:E:\java\test\ObjectSize\target\ObjectSize-1.0-SNAPSHOT.jar
不開(kāi)啟壓縮指針,減號(hào)就表示不開(kāi)啟
-javaagent:E:\java\test\ObjectSize\target\ObjectSize-1.0-SNAPSHOT.jar -XX:-UseCompressedClassPointers
指定把哪個(gè)jar文件當(dāng)成我這次運(yùn)行的虛擬機(jī)

運(yùn)行測(cè)量類(lèi):
import com.lnf.jvm.agent.ObjectSizeAgent; ? public class T03_SizeOfAnObject { ? ? ? public static void main(String[] args) {
? ? ? ? ? System.out.println(ObjectSizeAgent.sizeOf(new Object()));
? ? ? ? ? System.out.println(ObjectSizeAgent.sizeOf(new int[] {}));
? ? ? ? ? System.out.println(ObjectSizeAgent.sizeOf(new P()));
? ? ? } ? ? ? private static class P { ? ? ? ? ? ? ? ? ? ? ? ? ? //8 _markword
? ? ? ? ? ? ? ? ? ? ? ? ? //4 _oop指針
? ? ? ? ? int id; ? ? ? ? //4
? ? ? ? ? String name; ? ?//4
? ? ? ? ? int age; ? ? ? ?//4
? ? ? ? ? byte b1; ? ? ? ?//1
? ? ? ? ? byte b2; ? ? ? ?//1
? ? ? ? ? Object o; ? ? ? //4
? ? ? ? ? byte b3; ? ? ? ?//1
? ? ? }
? }

new Object()的長(zhǎng)度16 = 對(duì)象頭8+指針(不開(kāi)啟指針壓縮長(zhǎng)度是8)4 + padding 4
new int[] 的長(zhǎng)度 = 對(duì)象頭8+指針(不開(kāi)啟指針壓縮長(zhǎng)度是8)4 + int類(lèi)型 4 + padding 0(長(zhǎng)度正好16是8的2倍)
那么這里開(kāi)啟指針壓縮,我們也可不開(kāi)啟指針壓縮,通過(guò)JVM參數(shù)
-javaagent:E:\java\test\ObjectSize\target\ObjectSize-1.0-SNAPSHOT.jar -XX:-UseCompressedClassPointers

-XX:-UseCompressedClassPointers不開(kāi)啟指針壓縮來(lái)看下運(yùn)行結(jié)果

大家可以自行算一下,不開(kāi)啟壓縮就是8個(gè)字節(jié)
總結(jié)
實(shí)驗(yàn)到這就結(jié)束了,相信大家通過(guò)這個(gè)小實(shí)驗(yàn)可以認(rèn)識(shí)到一個(gè)對(duì)象在JVM內(nèi)存中的布局。大家可以動(dòng)手自己試一試,利用javaagent實(shí)測(cè)java對(duì)象大小。