一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務(wù)器之家:專注于服務(wù)器技術(shù)及軟件下載分享
分類導(dǎo)航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術(shù)|正則表達(dá)式|C/C++|IOS|C#|Swift|Android|VB|R語(yǔ)言|JavaScript|易語(yǔ)言|vb.net|

服務(wù)器之家 - 編程語(yǔ)言 - Java教程 - 新手初學(xué)Java對(duì)象內(nèi)存構(gòu)成

新手初學(xué)Java對(duì)象內(nèi)存構(gòu)成

2021-09-29 10:58landfill Java教程

這篇文章主要介紹了深入理解JVM之Java對(duì)象的創(chuàng)建、內(nèi)存布局、訪問(wèn)定位,結(jié)合實(shí)例形式詳細(xì)分析了Java對(duì)象的創(chuàng)建、內(nèi)存布局、訪問(wèn)定位相關(guān)概念、原理、操作技巧與注意事項(xiàng),需要的朋友可以參考下

Java對(duì)象內(nèi)存構(gòu)成

今天來(lái)講些抽象的東西 -- 對(duì)象頭,因?yàn)槲以趯W(xué)習(xí)的過(guò)程中發(fā)現(xiàn)很多地方都關(guān)聯(lián)到了對(duì)象頭的知識(shí)點(diǎn),例如JDK中的 synchronized鎖優(yōu)化 和 JVM 中對(duì)象年齡升級(jí)等等。要深入理解這些知識(shí)的原理,了解對(duì)象頭的概念很有必要,而且可以為后面分享 synchronized 原理和 JVM 知識(shí)的時(shí)候做準(zhǔn)備。

對(duì)象內(nèi)存構(gòu)成

Java 中通過(guò) new 關(guān)鍵字創(chuàng)建一個(gè)類的實(shí)例對(duì)象,對(duì)象存于內(nèi)存的堆中并給其分配一個(gè)內(nèi)存地址,那么是否想過(guò)如下這些問(wèn)題:

  • 這個(gè)實(shí)例對(duì)象是以怎樣的形態(tài)存在內(nèi)存中的?
  • 一個(gè)Object對(duì)象在內(nèi)存中占用多大?
  • 對(duì)象中的屬性是如何在內(nèi)存中分配的?

新手初學(xué)Java對(duì)象內(nèi)存構(gòu)成

在 JVM 中,Java對(duì)象保存在堆中時(shí),由以下三部分組成:

  • 對(duì)象頭(object header):包括了關(guān)于堆對(duì)象的布局、類型、GC狀態(tài)、同步狀態(tài)和標(biāo)識(shí)哈希碼的基本信息。Java對(duì)象和vm內(nèi)部對(duì)象都有一個(gè)共同的對(duì)象頭格式。
  • 實(shí)例數(shù)據(jù)(Instance Data):主要是存放類的數(shù)據(jù)信息,父類的信息,對(duì)象字段屬性信息。
  • 對(duì)齊填充(Padding):為了字節(jié)對(duì)齊,填充的數(shù)據(jù),不是必須的。

新手初學(xué)Java對(duì)象內(nèi)存構(gòu)成

對(duì)象頭

我們可以在Hotspot官方文檔中找到它的描述(下圖)。從中可以發(fā)現(xiàn),它是Java對(duì)象和虛擬機(jī)內(nèi)部對(duì)象都有的共同格式,由兩個(gè)字(計(jì)算機(jī)術(shù)語(yǔ))組成。另外,如果對(duì)象是一個(gè)Java數(shù)組,那在對(duì)象頭中還必須有一塊用于記錄數(shù)組長(zhǎng)度的數(shù)據(jù),因?yàn)樘摂M機(jī)可以通過(guò)普通Java對(duì)象的元數(shù)據(jù)信息確定Java對(duì)象的大小,但是從數(shù)組的元數(shù)據(jù)中無(wú)法確定數(shù)組的大小。

新手初學(xué)Java對(duì)象內(nèi)存構(gòu)成

它里面提到了對(duì)象頭由兩個(gè)字組成,這兩個(gè)字是什么呢?我們還是在上面的那個(gè)Hotspot官方文檔中往上看,可以發(fā)現(xiàn)還有另外兩個(gè)名詞的定義解釋,分別是 mark word 和 klass pointer。

新手初學(xué)Java對(duì)象內(nèi)存構(gòu)成

從中可以發(fā)現(xiàn)對(duì)象頭中那兩個(gè)字:第一個(gè)字就是 mark word,第二個(gè)就是 klass pointer。

Mark Word

用于存儲(chǔ)對(duì)象自身的運(yùn)行時(shí)數(shù)據(jù),如哈希碼(HashCode)、GC分代年齡、鎖狀態(tài)標(biāo)志、線程持有的鎖、偏向線程ID、偏向時(shí)間戳等等。

Mark Word在32位JVM中的長(zhǎng)度是32bit,在64位JVM中長(zhǎng)度是64bit。我們打開openjdk的源碼包,對(duì)應(yīng)路徑/openjdk/hotspot/src/share/vm/oops,Mark Word對(duì)應(yīng)到C++的代碼markOop.hpp,可以從注釋中看到它們的組成,本文所有代碼是基于Jdk1.8。

新手初學(xué)Java對(duì)象內(nèi)存構(gòu)成

Mark Word在不同的鎖狀態(tài)下存儲(chǔ)的內(nèi)容不同,在32位JVM中是這么存的

新手初學(xué)Java對(duì)象內(nèi)存構(gòu)成

在64位JVM中是這么存的

新手初學(xué)Java對(duì)象內(nèi)存構(gòu)成

雖然它們?cè)诓煌粩?shù)的JVM中長(zhǎng)度不一樣,但是基本組成內(nèi)容是一致的。

  • 鎖標(biāo)志位(lock):區(qū)分鎖狀態(tài),11時(shí)表示對(duì)象待GC回收狀態(tài), 只有最后2位鎖標(biāo)識(shí)(11)有效。
  • biased_lock:是否偏向鎖,由于無(wú)鎖和偏向鎖的鎖標(biāo)識(shí)都是 01,沒辦法區(qū)分,這里引入一位的偏向鎖標(biāo)識(shí)位。
  • 分代年齡(age):表示對(duì)象被GC的次數(shù),當(dāng)該次數(shù)到達(dá)閾值的時(shí)候,對(duì)象就會(huì)轉(zhuǎn)移到老年代。
  • 對(duì)象的hashcode(hash):運(yùn)行期間調(diào)用System.identityHashCode()來(lái)計(jì)算,延遲計(jì)算,并把結(jié)果賦值到這里。當(dāng)對(duì)象加鎖后,計(jì)算的結(jié)果31位不夠表示,在偏向鎖,輕量鎖,重量鎖,hashcode會(huì)被轉(zhuǎn)移到Monitor中。
  • 偏向鎖的線程ID(JavaThread):偏向模式的時(shí)候,當(dāng)某個(gè)線程持有對(duì)象的時(shí)候,對(duì)象這里就會(huì)被置為該線程的ID。 在后面的操作中,就無(wú)需再進(jìn)行嘗試獲取鎖的動(dòng)作。
  • epoch:偏向鎖在CAS鎖操作過(guò)程中,偏向性標(biāo)識(shí),表示對(duì)象更偏向哪個(gè)鎖。
  • ptr_to_lock_record:輕量級(jí)鎖狀態(tài)下,指向棧中鎖記錄的指針。當(dāng)鎖獲取是無(wú)競(jìng)爭(zhēng)的時(shí),JVM使用原子操作而不是OS互斥。這種技術(shù)稱為輕量級(jí)鎖定。在輕量級(jí)鎖定的情況下,JVM通過(guò)CAS操作在對(duì)象的標(biāo)題字中設(shè)置指向鎖記錄的指針。
  • ptr_to_heavyweight_monitor:重量級(jí)鎖狀態(tài)下,指向?qū)ο蟊O(jiān)視器Monitor的指針。如果兩個(gè)不同的線程同時(shí)在同一個(gè)對(duì)象上競(jìng)爭(zhēng),則必須將輕量級(jí)鎖定升級(jí)到Monitor以管理等待的線程。在重量級(jí)鎖定的情況下,JVM在對(duì)象的ptr_to_heavyweight_monitor設(shè)置指向Monitor的指針。

Klass Pointer#

即類型指針,是對(duì)象指向它的類元數(shù)據(jù)的指針,虛擬機(jī)通過(guò)這個(gè)指針來(lái)確定這個(gè)對(duì)象是哪個(gè)類的實(shí)例。

實(shí)例數(shù)據(jù)

如果對(duì)象有屬性字段,則這里會(huì)有數(shù)據(jù)信息。如果對(duì)象無(wú)屬性字段,則這里就不會(huì)有數(shù)據(jù)。根據(jù)字段類型的不同占不同的字節(jié),例如boolean類型占1個(gè)字節(jié),int類型占4個(gè)字節(jié)等等;

對(duì)齊數(shù)據(jù)

對(duì)象可以有對(duì)齊數(shù)據(jù)也可以沒有。默認(rèn)情況下,Java虛擬機(jī)堆中對(duì)象的起始地址需要對(duì)齊至8的倍數(shù)。如果一個(gè)對(duì)象用不到8N個(gè)字節(jié)則需要對(duì)其填充,以此來(lái)補(bǔ)齊對(duì)象頭和實(shí)例數(shù)據(jù)占用內(nèi)存之后剩余的空間大小。如果對(duì)象頭和實(shí)例數(shù)據(jù)已經(jīng)占滿了JVM所分配的內(nèi)存空間,那么就不用再進(jìn)行對(duì)齊填充了。

所有的對(duì)象分配的字節(jié)總SIZE需要是8的倍數(shù),如果前面的對(duì)象頭和實(shí)例數(shù)據(jù)占用的總SIZE不滿足要求,則通過(guò)對(duì)齊數(shù)據(jù)來(lái)填滿。

為什么要對(duì)齊數(shù)據(jù)?字段內(nèi)存對(duì)齊的其中一個(gè)原因,是讓字段只出現(xiàn)在同一CPU的緩存行中。如果字段不是對(duì)齊的,那么就有可能出現(xiàn)跨緩存行的字段。也就是說(shuō),該字段的讀取可能需要替換兩個(gè)緩存行,而該字段的存儲(chǔ)也會(huì)同時(shí)污染兩個(gè)緩存行。這兩種情況對(duì)程序的執(zhí)行效率而言都是不利的。其實(shí)對(duì)其填充的最終目的是為了計(jì)算機(jī)高效尋址。

至此,我們已經(jīng)了解了對(duì)象在堆內(nèi)存中的整體結(jié)構(gòu)布局,如下圖所示

新手初學(xué)Java對(duì)象內(nèi)存構(gòu)成

Talk is cheap, show me code

概念的東西是抽象的,你說(shuō)它是這樣組成的,就真的是嗎?學(xué)習(xí)是需要持懷疑的態(tài)度的,任何理論和概念只有自己證實(shí)和實(shí)踐之后才能接受它。還好 openjdk 給我們提供了一個(gè)工具包,可以用來(lái)獲取對(duì)象的信息和虛擬機(jī)的信息,我們只需引入 jol-core 依賴,如下

  1. <dependency>
  2. <groupId>org.openjdk.jol</groupId>
  3. <artifactId>jol-core</artifactId>
  4. <version>0.8</version>
  5. </dependency>

jol-core 常用的三個(gè)方法

  • ClassLayout.parseInstance(object).toPrintable():查看對(duì)象內(nèi)部信息.
  • GraphLayout.parseInstance(object).toPrintable():查看對(duì)象外部信息,包括引用的對(duì)象.
  • GraphLayout.parseInstance(object).totalSize():查看對(duì)象總大小.

普通對(duì)象#

為了簡(jiǎn)單化,我們不用復(fù)雜的對(duì)象,自己創(chuàng)建一個(gè)類 D,先看無(wú)屬性字段的時(shí)候

  1. public class D {
  2. }

通過(guò) jol-core 的 api,我們將對(duì)象的內(nèi)部信息打印出來(lái)

  1. public static void main(String[] args) {
  2. D d = new D();
  3. System.out.println(ClassLayout.parseInstance(d).toPrintable());
  4. }

最后的打印結(jié)果為

新手初學(xué)Java對(duì)象內(nèi)存構(gòu)成

可以看到有 OFFSET、SIZE、TYPE DESCRIPTION、VALUE 這幾個(gè)名詞頭,它們的含義分別是

  • OFFSET:偏移地址,單位字節(jié);
  • SIZE:占用的內(nèi)存大小,單位為字節(jié);
  • TYPE DESCRIPTION:類型描述,其中object header為對(duì)象頭;
  • VALUE:對(duì)應(yīng)內(nèi)存中當(dāng)前存儲(chǔ)的值,二進(jìn)制32位;

新手初學(xué)Java對(duì)象內(nèi)存構(gòu)成

可以看到,d對(duì)象實(shí)例共占據(jù)16byte,對(duì)象頭(object header)占據(jù)12byte(96bit),其中 mark word占8byte(64bit),klass pointe 占4byte,另外剩余4byte是填充對(duì)齊的。

這里由于默認(rèn)開啟了指針壓縮 ,所以對(duì)象頭占了12byte,具體的指針壓縮的概念這里就不再闡述了,感興趣的讀者可以自己查閱下官方文檔。jdk8版本是默認(rèn)開啟指針壓縮的,可以通過(guò)配置vm參數(shù)開啟關(guān)閉指針壓縮,-XX:-UseCompressedOops

新手初學(xué)Java對(duì)象內(nèi)存構(gòu)成

如果關(guān)閉指針壓縮重新打印對(duì)象的內(nèi)存布局,可以發(fā)現(xiàn)總SIZE變大了,從下圖中可以看到,對(duì)象頭所占用的內(nèi)存大小變?yōu)?6byte(128bit),其中 mark word占8byte,klass pointe 占8byte,無(wú)對(duì)齊填充。

新手初學(xué)Java對(duì)象內(nèi)存構(gòu)成

開啟指針壓縮可以減少對(duì)象的內(nèi)存使用。從兩次打印的D對(duì)象布局信息來(lái)看,關(guān)閉指針壓縮時(shí),對(duì)象頭的SIZE增加了4byte,這里由于D對(duì)象是無(wú)屬性的,讀者可以試試增加幾個(gè)屬性字段來(lái)看下,這樣會(huì)明顯的發(fā)現(xiàn)SIZE增長(zhǎng)。因此開啟指針壓縮,理論上來(lái)講,大約能節(jié)省百分之五十的內(nèi)存。jdk8及以后版本已經(jīng)默認(rèn)開啟指針壓縮,無(wú)需配置。

數(shù)組對(duì)象

上面使用的是普通對(duì)象,我們來(lái)看下數(shù)組對(duì)象的內(nèi)存布局,比較下有什么異同

  1. public static void main(String[] args) {
  2. int[] a = {1};
  3. System.out.println(ClassLayout.parseInstance(a).toPrintable());
  4. }

打印的內(nèi)存布局信息,如下

新手初學(xué)Java對(duì)象內(nèi)存構(gòu)成

可以看到這時(shí)總SIZE為共24byte,對(duì)象頭占16byte,其中Mark Work占8byte,Klass Point 占4byte,array length 占4byte,因?yàn)槔锩嬷挥幸粋€(gè)int 類型的1,所以數(shù)組對(duì)象的實(shí)例數(shù)據(jù)占據(jù)4byte,剩余對(duì)齊填充占據(jù)4byte。

總結(jié)

經(jīng)過(guò)以上的內(nèi)容我們了解了對(duì)象在內(nèi)存中的布局,了解對(duì)象的內(nèi)存布局和對(duì)象頭的概念,特別是對(duì)象頭的Mark Word的內(nèi)容,在我們后續(xù)分析 synchronize 鎖優(yōu)化 和 JVM 垃圾回收年齡代的時(shí)候會(huì)有很大作用。

JVM中大家是否還記得對(duì)象在Suvivor中每熬過(guò)一次MinorGC,年齡就增加1,當(dāng)它的年齡增加到一定程度后就會(huì)被晉升到老年代中,這個(gè)次數(shù)默認(rèn)是15歲,有想過(guò)為什么是15嗎?在Mark Word中可以發(fā)現(xiàn)標(biāo)記對(duì)象分代年齡的分配的空間是4bit,而4bit能表示的最大數(shù)就是2^4-1 = 15。

本派文章就到這里了,希望能給你帶來(lái)幫助,也希望你能夠多多關(guān)注我們的更多內(nèi)容!

原文鏈接:https://www.cnblogs.com/land-fill/p/14968737.html

延伸 · 閱讀

精彩推薦
  • Java教程升級(jí)IDEA后Lombok不能使用的解決方法

    升級(jí)IDEA后Lombok不能使用的解決方法

    最近看到提示IDEA提示升級(jí),尋思已經(jīng)有好久沒有升過(guò)級(jí)了。升級(jí)完畢重啟之后,突然發(fā)現(xiàn)好多錯(cuò)誤,本文就來(lái)介紹一下如何解決,感興趣的可以了解一下...

    程序猿DD9332021-10-08
  • Java教程Java實(shí)現(xiàn)搶紅包功能

    Java實(shí)現(xiàn)搶紅包功能

    這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)搶紅包功能,采用多線程模擬多人同時(shí)搶紅包,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙...

    littleschemer13532021-05-16
  • Java教程Java BufferWriter寫文件寫不進(jìn)去或缺失數(shù)據(jù)的解決

    Java BufferWriter寫文件寫不進(jìn)去或缺失數(shù)據(jù)的解決

    這篇文章主要介紹了Java BufferWriter寫文件寫不進(jìn)去或缺失數(shù)據(jù)的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望...

    spcoder14552021-10-18
  • Java教程xml與Java對(duì)象的轉(zhuǎn)換詳解

    xml與Java對(duì)象的轉(zhuǎn)換詳解

    這篇文章主要介紹了xml與Java對(duì)象的轉(zhuǎn)換詳解的相關(guān)資料,需要的朋友可以參考下...

    Java教程網(wǎng)2942020-09-17
  • Java教程小米推送Java代碼

    小米推送Java代碼

    今天小編就為大家分享一篇關(guān)于小米推送Java代碼,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧...

    富貴穩(wěn)中求8032021-07-12
  • Java教程Java使用SAX解析xml的示例

    Java使用SAX解析xml的示例

    這篇文章主要介紹了Java使用SAX解析xml的示例,幫助大家更好的理解和學(xué)習(xí)使用Java,感興趣的朋友可以了解下...

    大行者10067412021-08-30
  • Java教程Java8中Stream使用的一個(gè)注意事項(xiàng)

    Java8中Stream使用的一個(gè)注意事項(xiàng)

    最近在工作中發(fā)現(xiàn)了對(duì)于集合操作轉(zhuǎn)換的神器,java8新特性 stream,但在使用中遇到了一個(gè)非常重要的注意點(diǎn),所以這篇文章主要給大家介紹了關(guān)于Java8中S...

    阿杜7472021-02-04
  • Java教程20個(gè)非常實(shí)用的Java程序代碼片段

    20個(gè)非常實(shí)用的Java程序代碼片段

    這篇文章主要為大家分享了20個(gè)非常實(shí)用的Java程序片段,對(duì)java開發(fā)項(xiàng)目有所幫助,感興趣的小伙伴們可以參考一下 ...

    lijiao5352020-04-06
主站蜘蛛池模板: 亚洲精品成人AV在线观看爽翻 | 精品视频国产 | 精品综合久久久久久97超人 | 99久久免费国产特黄 | 精品精品国产自在久久高清 | 国产人成激情视频在线观看 | zol中关村在线 | 色播开心网 | 午夜伦伦电影理论片大片 | 亚洲mv国产精品mv日本mv | 亚洲欧美自偷自拍另类小说 | 视频二区 素人 制服 国产 | 精品视频一区二区观看 | 边摸边吃奶边做爽视频免费 | 视频在线网站 | 欧美 国产 日韩 第一页 | 日本在线一区 | 天美影视传媒mv直接看 | 精品久久日日躁夜夜躁AV | 无码欧美喷潮福利XXXX | 色天天综合网色鬼综合 | 久久天天综合 | 校花被老头夺去第一次动图 | 任我鲁精品视频精品 | 波多野 在线| 日本人和黑人一级纶理片 | 国产午夜亚洲精品理论片不卡 | 高清在线免费 | 色播影院性播影院私人影院 | 久久热在线视频精品1 | 美女跪式抽搐gif动态图 | 美女张开下身让男人桶 | 国产在线精品成人一区二区三区 | 久久这里只有精品视频e | 日本国产最新一区二区三区 | 国产欧美亚洲精品第一页青草 | 973影院| 色戒西瓜| 国产精品香蕉夜间视频免费播放 | www.亚洲天堂| 日本美女动态图片 |