最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

 更新時間:2021年07月07日 17:04:52   作者:landfill  
這篇文章主要介紹了深入理解JVM之Java對象的創(chuàng)建、內(nèi)存布局、訪問定位,結(jié)合實例形式詳細(xì)分析了Java對象的創(chuàng)建、內(nèi)存布局、訪問定位相關(guān)概念、原理、操作技巧與注意事項,需要的朋友可以參考下

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

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

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

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

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

在 JVM 中,Java對象保存在堆中時,由以下三部分組成:

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

對象頭

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

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

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

Mark Word

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

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

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

在64位JVM中是這么存的

雖然它們在不同位數(shù)的JVM中長度不一樣,但是基本組成內(nèi)容是一致的。

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

Klass Pointer

即類型指針,是對象指向它的類元數(shù)據(jù)的指針,虛擬機通過這個指針來確定這個對象是哪個類的實例。

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

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

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

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

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

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

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

Talk is cheap, show me code

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

<dependency>
  <groupId>org.openjdk.jol</groupId>
  <artifactId>jol-core</artifactId>
  <version>0.8</version>
</dependency>

jol-core 常用的三個方法

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

普通對象

為了簡單化,我們不用復(fù)雜的對象,自己創(chuàng)建一個類 D,先看無屬性字段的時候

public class D {
}

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

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

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

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

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

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

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

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

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

數(shù)組對象

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

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

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

可以看到這時總SIZE為共24byte,對象頭占16byte,其中Mark Work占8byte,Klass Point 占4byte,array length 占4byte,因為里面只有一個int 類型的1,所以數(shù)組對象的實例數(shù)據(jù)占據(jù)4byte,剩余對齊填充占據(jù)4byte。

總結(jié)

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

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

本篇文章就到這里了,希望能給你帶來幫助,也希望你能夠多多關(guān)注腳本之家的更多內(nèi)容!

相關(guān)文章

  • Spring mvc結(jié)果跳轉(zhuǎn)方法詳解

    Spring mvc結(jié)果跳轉(zhuǎn)方法詳解

    這篇文章主要介紹了Spring mvc結(jié)果跳轉(zhuǎn)方法詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-03-03
  • java 驗證碼的生成實現(xiàn)

    java 驗證碼的生成實現(xiàn)

    這篇文章主要介紹了java 驗證碼的生成實現(xiàn)的相關(guān)資料,需要的朋友可以參考下
    2017-08-08
  • Java使用HttpClient實現(xiàn)Post請求實例

    Java使用HttpClient實現(xiàn)Post請求實例

    本篇文章主要介紹了Java使用HttpClient實現(xiàn)Post請求實例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-02-02
  • 10個Java程序員熟悉的面向?qū)ο笤O(shè)計原則

    10個Java程序員熟悉的面向?qū)ο笤O(shè)計原則

    這篇文章主要為大家詳細(xì)介紹了Java程序員應(yīng)當(dāng)知道的10個面向?qū)ο笤O(shè)計原則,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-03-03
  • Java排序算法之桶排序算法解析

    Java排序算法之桶排序算法解析

    這篇文章主要介紹了Java排序算法之桶排序算法解析,桶排序 (Bucket sort)或所謂的箱排序,是一個排序算法,工作原理是將數(shù)組分到有限數(shù)量的桶子里,每個桶子再個別排序,有可能再使用別的排序算法或是以遞歸方式繼續(xù)使用桶排序進行排序,需要的朋友可以參考下
    2023-10-10
  • Android中幾種圖片特效的處理的實現(xiàn)方法

    Android中幾種圖片特效的處理的實現(xiàn)方法

    這篇文章主要介紹了 Android中幾種圖片特效的處理的實現(xiàn)方法的相關(guān)資料,這里有放大縮小圖片,獲得圓角圖片,獲得帶倒影圖片的幾種方法,需要的朋友可以參考下
    2017-08-08
  • Javaweb EL自定義函數(shù)開發(fā)及代碼實例

    Javaweb EL自定義函數(shù)開發(fā)及代碼實例

    這篇文章主要介紹了Javaweb EL自定義函數(shù)開發(fā)及代碼實例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-06-06
  • Java并發(fā)編程線程間通訊實現(xiàn)過程詳解

    Java并發(fā)編程線程間通訊實現(xiàn)過程詳解

    這篇文章主要介紹了Java并發(fā)編程線程間通訊實現(xiàn)過程詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-05-05
  • java實現(xiàn)合并兩個已經(jīng)排序的列表實例代碼

    java實現(xiàn)合并兩個已經(jīng)排序的列表實例代碼

    這篇文章主要介紹了java實現(xiàn)合并兩個已經(jīng)排序的列表實例代碼,有需要的朋友可以參考一下
    2013-12-12
  • Java?并發(fā)編程之ForkJoin框架

    Java?并發(fā)編程之ForkJoin框架

    這篇文章主要為大家介紹了Java?ForkJoin框架,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助,希望能夠給你帶來幫助
    2021-11-11

最新評論

临安市| 漠河县| 东明县| 墨玉县| 承德县| 峨眉山市| 肇州县| 惠安县| 南投县| 彭泽县| 千阳县| 桃园市| 永嘉县| 正阳县| 江油市| 天峨县| 大同县| 小金县| 阿合奇县| 阿荣旗| 明水县| 洞口县| 翼城县| 祥云县| 梓潼县| 绵竹市| 文化| 安化县| 堆龙德庆县| 紫云| 双城市| 西昌市| 同江市| 巴南区| 临武县| 宽城| 华池县| 白水县| 航空| 乐清市| 鹿邑县|