Java Object Layout – Java對象的內(nèi)存布局

java對象實例64位操作系統(tǒng).png

在 Java 程序中,我們擁有多種新建對象的方式曹洽。除了最為常見的 new 語句之外瓤檐,我們還可以通過反射機制、Object.clone 方法排截、反序列化以及 Unsafe.allocateInstance 方法來新建對象

Object.clone 方法和反序列化通過直接復(fù)制已有的數(shù)據(jù)嫌蚤,來初始化新建對象的實例字段。Unsafe.allocateInstance 方法則沒有初始化實例字段断傲,而 new 語句和反射機制脱吱,則是通過調(diào)用構(gòu)造器來初始化實例字段。

new 語句為例,字節(jié)碼將包含用來請求內(nèi)存的 new 指令认罩,以及用來調(diào)用構(gòu)造器的 invokespecial 指令箱蝠。

public class TestFoo {
    public static void main(String[] args) {
        TestFoo foo = new TestFoo();
    }
}

對應(yīng)字節(jié)碼

  public static main([Ljava/lang/String;)V
   L0
    LINENUMBER 5 L0
    NEW top/zcwfeng/java/test/TestFoo
    DUP
    INVOKESPECIAL top/zcwfeng/java/test/TestFoo.<init> ()V
    ASTORE 1

如果一個類沒有定義任何構(gòu)造器的話,java編譯器會自動添加一個無參數(shù)的構(gòu)造器

public <init>()V
   L0
    LINENUMBER 3 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
    RETURN

壓縮指針

在 Java 虛擬機中垦垂,每個 Java 對象都有一個對象頭(object header)宦搬,這個由標記字段(Mark Word)和類型指針(Klass Pointer)所構(gòu)成。其中劫拗,標記字段用以存儲 Java 虛擬機有關(guān)該對象的運行數(shù)據(jù)间校,如哈希碼、GC 信息以及鎖信息页慷,而類型指針則指向該對象的類憔足。

在 64 位的 Java 虛擬機中,對象頭的標記字段占 64 位酒繁,而類型指針又占了 64 位滓彰。也就是說,每一個 Java 對象在內(nèi)存中的額外開銷就是 16 個字節(jié)州袒。以 Integer 類為例揭绑,它僅有一個 int 類型的私有字段,占 4 個字節(jié)郎哭。因此洗做,每一個 Integer 對象的額外內(nèi)存開銷至少是 400%弓叛。這也是為什么 Java 要引入基本類型的原因之一。

為了盡量較少對象的內(nèi)存使用量诚纸,64 位 Java 虛擬機引入了壓縮指針 [1] 的概念(對應(yīng)虛擬機選項 -XX:+UseCompressedOops,默認開啟)陈惰,將堆中原本 64 位的 Java 對象指針壓縮成 32 位的畦徘。

這樣一來,對象頭中的類型指針也會被壓縮成 32 位抬闯,使得對象頭的大小從 16 字節(jié)降至 12 字節(jié)井辆。當然,壓縮指針不僅可以作用于對象頭的類型指針溶握,還可以作用于引用類型的字段杯缺,以及引用類型數(shù)組。

上面是官方的解釋睡榆。我們弄明白幾個問題:

32位操作系統(tǒng)可以尋址到多大內(nèi)存

答:4g 因為 2^32=4 * 1024 * 1024=4g

64位呢萍肆?

2的64次方bai:18446744073709551616

這個數(shù)有點大,計算器一般算不出來胀屿,編程的話用long值才能計算到2的62次方

答:64位過長塘揣,給我們尋址帶寬和對象內(nèi)引用造成了負擔(dān)

一個對象占用的字節(jié)數(shù)

對象頭:
32位系統(tǒng),占用 8 字節(jié)(markWord4字節(jié)+kclass4字節(jié))
64位系統(tǒng)宿崭,開啟 UseCompressedOops(壓縮指針)時亲铡,占用 12 字節(jié),否則是16字節(jié)(markWord8字節(jié)+kclass8字節(jié)葡兑,開啟時markWord8字節(jié)+kclass4字節(jié))

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

boolean 1
byte 1
short 2
char 2
int 4
float 4
long 8
double 8

引用類型

32位系統(tǒng)占4字節(jié) (因為此引用類型要去方法區(qū)中找類信息,所以地址為32位即4字節(jié)同理64位是8字節(jié))
64位系統(tǒng)奖蔓,開啟 UseCompressedOops時,占用4字節(jié)讹堤,否則是8字節(jié)

對齊填充

如果對象頭+實例數(shù)據(jù)的值不是8的倍數(shù)吆鹤,那么會補上一些,補夠8的倍數(shù)

32位操作系統(tǒng) 花費的內(nèi)存空間為
對象頭-8字節(jié) + 實例數(shù)據(jù) int類型-4字節(jié) + 引用類型-4字節(jié)+補充0字節(jié)(16是8的倍數(shù)) 16個字節(jié)

64位操作系統(tǒng)(未開啟指針壓縮)
對象頭-16字節(jié) + 實例數(shù)據(jù) int類型-4字節(jié) + 引用類型-8字節(jié)+補充4字節(jié)(28不是8的倍數(shù)補充4字節(jié)到達32字節(jié)) 32個字節(jié)

同樣的對象需要將近兩倍的容量,(實際平均1.5倍)

64位開啟壓縮指針

對象頭-12字節(jié) + 實例數(shù)據(jù) int類型-4字節(jié) + 引用類型-4字節(jié)+補充0字節(jié)=24個字節(jié)---減緩堆空間的壓力(同樣的內(nèi)存更不容易發(fā)生oom)

JVM的實現(xiàn)方式是
不再保存所有引用蜕劝,而是每隔8個字節(jié)保存一個引用檀头。例如,原來保存每個引用0岖沛、1暑始、2…,現(xiàn)在只保存0婴削、8廊镜、16…。因此唉俗,指針壓縮后嗤朴,并不是所有引用都保存在堆中配椭,而是以8個字節(jié)為間隔保存引用。
在實現(xiàn)上雹姊,堆中的引用其實還是按照0x0股缸、0x1、0x2…進行存儲吱雏。只不過當引用被存入64位的寄存器時敦姻,JVM將其左移3位(相當于末尾添加3個0),例如0x0歧杏、0x1镰惦、0x2…分別被轉(zhuǎn)換為0x0、0x8犬绒、0x10旺入。而當從寄存器讀出時,JVM又可以右移3位凯力,丟棄末尾的0茵瘾。(oop在堆中是32位,在寄存器中是35位沮协,2的35次方=32G龄捡。也就是說,使用32位慷暂,來達到35位oop所能引用的堆內(nèi)存空間)

哪些信息會被壓縮聘殖?

1.對象的全局靜態(tài)變量(即類屬性)
2.對象頭信息:64位平臺下,原生對象頭大小為16字節(jié)行瑞,壓縮后為12字節(jié)
3.對象的引用類型:64位平臺下奸腺,引用類型本身大小為8字節(jié),壓縮后為4字節(jié)
4.對象數(shù)組類型:64位平臺下血久,數(shù)組類型本身大小為24字節(jié)突照,壓縮后16字節(jié)

哪些信息不會被壓縮?

1.指向非Heap的對象指針
2.局部變量氧吐、傳參讹蘑、返回值、NULL指針

在JVM中(不管是32位還是64位)筑舅,對象已經(jīng)按8字節(jié)邊界對齊了座慰。對于大部分處理器,這種對齊方案都是最優(yōu)的翠拣。所以版仔,使用壓縮的oop并不會帶來什么損失,反而提升了性能。

看一個實例

class A {
    long l;
    int i;
}

class B extends A {
    long l;
    int i;
}


開啟壓縮指針 開啟(-XX:+UseCompressedOops)  默認開啟
> Task :TestFoo.main()
------------B---------------
# WARNING: Unable to get Instrumentation. Dynamic Attach failed. You may add this JAR as -javaagent manually, or supply -Djdk.attach.allowAttachSelf
top.zcwfeng.java.test.B object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           50 04 06 00 (01010000 00000100 00000110 00000000) (394320)
     12     4    int A.i                                       0
     16     8   long A.l                                       0
     24     8   long B.l                                       0
     32     4    int B.i                                       0
     36     4        (loss due to the next object alignment)
Instance size: 40 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total


================

關(guān)閉壓縮指針 關(guān)閉(-XX:-UseCompressedOops) 可以關(guān)閉壓縮指針
> Task :TestFoo.main()
------------B---------------
# WARNING: Unable to get Instrumentation. Dynamic Attach failed. You may add this JAR as -javaagent manually, or supply -Djdk.attach.allowAttachSelf
top.zcwfeng.java.test.B object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           48 12 e0 a1 (01001000 00010010 11100000 10100001) (-1579150776)
     12     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
     16     8   long A.l                                       0
     24     4    int A.i                                       0
     28     4        (alignment/padding gap)                  
     32     8   long B.l                                       0
     40     4    int B.i                                       0
     44     4        (loss due to the next object alignment)
Instance size: 48 bytes
Space losses: 4 bytes internal + 4 bytes external = 8 bytes total

字段重排列

Java 虛擬機重新分配字段的先后順序蛮粮,以達到內(nèi)存對齊的目的益缎。Java 虛擬機中有三種排列方法(對應(yīng) Java 虛擬機選項 -XX:FieldsAllocationStyle,默認值為 1)然想,但都會遵循如下兩個規(guī)則莺奔。

其一,如果一個字段占據(jù) C 個字節(jié)又沾,那么該字段的偏移量需要對齊至 NC弊仪。這里偏移量指的是字段地址與對象的起始地址差值。

以 long 類為例杖刷,它僅有一個 long 類型的實例字段。在使用了壓縮指針的 64 位虛擬機中驳癌,盡管對象頭的大小為 12 個字節(jié)滑燃,該 long 類型字段的偏移量也只能是 16,而中間空著的 4 個字節(jié)便會被浪費掉颓鲜。

其二表窘,子類所繼承字段的偏移量,需要與父類對應(yīng)字段的偏移量保持一致甜滨。

在具體實現(xiàn)中乐严,Java 虛擬機還會對齊子類字段的起始位置。對于使用了壓縮指針的 64 位虛擬機衣摩,子類第一個字段需要對齊至 4N昂验;而對于關(guān)閉了壓縮指針的 64 位虛擬機,子類第一個字段則需要對齊至 8N艾扮。

上面的分析既琴,加入了工具JOL的幫助

gradle 配置

implementation 'org.openjdk.jol:jol-core:0.14'

java 環(huán)境

java 11.0.10 2021-01-19 LTS
Java(TM) SE Runtime Environment 18.9 (build 11.0.10+8-LTS-162)
Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11.0.10+8-LTS-162, mixed mode)

當然Java8 版本有的也可以,我的失敗了泡嘴,為了方便所有我選擇存在的環(huán)境11

然后調(diào)用可以分析:

 System.out.println("------------B---------------");
        B o = new B();
        String s = ClassLayout.parseInstance(o).toPrintable();
        System.out.println(s);
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
禁止轉(zhuǎn)載甫恩,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者。
  • 序言:七十年代末酌予,一起剝皮案震驚了整個濱河市磺箕,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌抛虫,老刑警劉巖松靡,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異莱褒,居然都是意外死亡击困,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來阅茶,“玉大人蛛枚,你說我怎么就攤上這事×嘲В” “怎么了蹦浦?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長撞蜂。 經(jīng)常有香客問我盲镶,道長,這世上最難降的妖魔是什么蝌诡? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任溉贿,我火速辦了婚禮,結(jié)果婚禮上浦旱,老公的妹妹穿的比我還像新娘宇色。我一直安慰自己,他們只是感情好颁湖,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布宣蠕。 她就那樣靜靜地躺著,像睡著了一般甥捺。 火紅的嫁衣襯著肌膚如雪抢蚀。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天镰禾,我揣著相機與錄音皿曲,去河邊找鬼。 笑死羡微,一個胖子當著我的面吹牛谷饿,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播妈倔,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼博投,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了盯蝴?” 一聲冷哼從身側(cè)響起毅哗,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎捧挺,沒想到半個月后虑绵,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡闽烙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年翅睛,在試婚紗的時候發(fā)現(xiàn)自己被綠了声搁。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡捕发,死狀恐怖疏旨,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情扎酷,我是刑警寧澤檐涝,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站法挨,受9級特大地震影響谁榜,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜凡纳,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一窃植、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧荐糜,春花似錦撕瞧、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽巩掺。三九已至偏序,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間胖替,已是汗流浹背研儒。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留独令,地道東北人端朵。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像燃箭,于是被迫代替她去往敵國和親冲呢。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容