摘要:聊聊對象在內(nèi)存中的大小本文討論的對象在內(nèi)存中的大小指的是在堆中的大小未特殊說明,提到的地方都指的是,版本。而實際是運行方法會看到結(jié)果對象實例總大小,空間損失。數(shù)組也是對象,但數(shù)組的中包含有一個類型的值,又多占了的空間,所以數(shù)組的大小是。
聊聊Java對象在內(nèi)存中的大小
本文討論的Java對象在內(nèi)存中的大小指的是在堆(Heap)中的大小;未特殊說明,提到JVM的地方都指的是:Java HotSpot(TM) 64-Bit Server VM,版本:1.8.0_131。
Java中Object的組成:
Object = Header + Primitive Fields + Reference Fields + Alignment & Padding`
Header由兩部分組成:標(biāo)記部分(Mark Word)和原始對象引用(Klass Pointer/Object Original Pointer)- mark word & klass pointer。
標(biāo)記部分的大小是一個word size(64-bit JVM上是8 bytes,32-bit JVM上是4 bytes),包括了該對象的identity hash code和一些標(biāo)記(比如鎖和年代信息)。
原始對象引用在32-bit JVM上的大小是4 bytes,在64-bit JVM上可以是4 bytes,也可以是8 bytes,由JVM參數(shù)“是否壓縮原始對象”決定,在HotSpot中是UseCompressedOops參數(shù)(jdk1.8 和jdk1.9默認(rèn)是開啟的)。
Primitive Fields && Reference Fields
類型 | 大小 |
---|---|
Object Reference | word size |
byte | 1 byte |
boolean | 1 byte |
char | 2 bytes |
short | 2 bytes |
int | 4 bytes |
float | 4 bytes |
double | 8 bytes |
long | 8 bytes |
對齊(Alignment)和補齊(Padding)
對齊,任何對象都是以8 bytes的粒度來對齊的。
怎么理解這句話呢?請看一個例子,new Object()產(chǎn)生的對象的大小是多少呢?12 bytes的header,但對齊必須是8的倍數(shù),還有4 bytes的alignment,所以對象的大小是16 bytes.
補齊,補齊的粒度是4 bytes。
可以簡單理解為,JVM分配內(nèi)存空間一次最少分配8 bytes,對象中字段對齊的最小粒度為4 bytes。
準(zhǔn)備工作
本文使用Maven管理Jar包,源碼在這里。
pom.xml中引入JOL(Java Object Layout, 使用實例 )依賴,用于展示對象在Heap中的分布(layout):
org.openjdk.jol jol-core 0.9
第一個測試:
public static void main(String[] args) { System.out.println(VM.current().details()); }
執(zhí)行后,會輸出:
# Running 64-bit HotSpot VM. # Using compressed oop with 3-bit shift. # Using compressed klass with 3-bit shift. # WARNING | Compressed references base/shifts are guessed by the experiment! # WARNING | Therefore, computed addresses are just guesses, and ARE NOT RELIABLE. # WARNING | Make sure to attach Serviceability Agent to get the reliable addresses. # Objects are 8 bytes aligned. // 以 8 bytes的粒度對齊 # Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes] // 分別對應(yīng)[Oop(Object Original Pointer), boolean, byte, char, short, int, float, long, double]的大小 # Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes] // 數(shù)組中元素的大小,分別對應(yīng)的是[Oop(Object Original Pointer), boolean, byte, char, short, int, float, long, double]
對象在Heap中的分布遵循的規(guī)則:
重排序, JVM在Heap中給對象布局時,會對field進(jìn)行重排序,以節(jié)省空間。
例-1,對于類:
public class Reorder { private byte a; private int b; private boolean c; private float d; private Object e; public static void main(String[] args) { System.out.println(ClassLayout.parseClass(Reorder.class).toPrintable()); } }
如果沒有重排序,對象的分布會是這個樣子的:
objectsize.Reorder object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 12 (object header) N/A 12 1 byte Reorder.a N/A 13 3 (alignment/padding gap) 16 4 int Reorder.b N/A 20 1 boolean Reorder.c N/A 21 3 (alignment/padding gap) 24 4 float Reorder.d N/A 28 2 char Reorder.e N/A 30 2 (loss due to the next object alignment) Instance size: 32 bytes Space losses: 6 bytes internal + 2 bytes external = 8 bytes total
對象實例總大小:32 bytes,空間損失:8 bytes。
而實際是(運行main方法會看到結(jié)果):
objectsize.Reorder object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 12 (object header) N/A 12 4 int Reorder.b N/A 16 4 float Reorder.d N/A 20 2 char Reorder.e N/A 22 1 byte Reorder.a N/A 23 1 boolean Reorder.c N/A Instance size: 24 bytes Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
對象實例總大小:24 bytes,空間損失:0 bytes。
為了避免空間浪費,一般情況下,field分配的優(yōu)先依次順序是:double > long > int > float > char > short > byte > boolean > object reference。
注意到了沒,這里有個基本的原則是:盡可能先分配占用空間大的類型(除了object reference)。這里的盡可能有兩層含義:
在同等優(yōu)先級情況下,按這個順序分配。 例-2:
public class Order { private int ignoreMeTentatively; private byte a; private boolean b; private char c; private short d; private int e; private float f; private double g; private long h; private Object i; public static void main(String[] args) { System.out.println(ClassLayout.parseClass(Order.class).toPrintable()); } }
這個類的實例在內(nèi)存中分布是:
objectsize.Reorder object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 12 (object header) N/A 12 4 int Reorder.b N/A 16 4 float Reorder.d N/A 20 2 char Reorder.e N/A 22 1 byte Reorder.a N/A 23 1 boolean Reorder.c N/A Instance size: 24 bytes Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
請先忽略ignoreMeTentatively字段,可以驗證類型分配的順序。
在考慮到補齊(Padding)的情況下,排在后面的類型有可能比排在前面的優(yōu)先級更高。
回過頭來看例-1和例-2,會發(fā)現(xiàn)header后的字一個field(offset 12)都是int類型的。為什么呢?
這就是Alignment和Padding共同作用的結(jié)果。
JVM每次最少分配8 bytes的空間,而header的大小是12。
也就是說,已經(jīng)分配了16 bytes的空間了,如果嚴(yán)格按照前面說的那個順序,最先分配一個double類型的field,就需要在這之前先分配4 bytes的空間來補齊,也就這4 bytes的空間就白白浪費了。
這中情況下,<=Padding Size(4 bytes)的類型的優(yōu)先級就高于大小>Padding Size的類型了。
而在所有大小<=Padding Size的類型中,int的優(yōu)先級又是最高的,所以header后的第一個field是int。
為了進(jìn)一步理解,再來看個例子,例-3:
public class Padding { private char a; private boolean b; private long c; private Object d; public static void main(String[] args) { System.out.println(ClassLayout.parseClass(Padding.class).toPrintable()); } }
這個類的實例在內(nèi)存中分布是:
objectsize.Padding object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 12 (object header) N/A 12 2 char Padding.a N/A 14 1 boolean Padding.b N/A 15 1 (alignment/padding gap) 16 8 long Padding.c N/A 24 4 java.lang.Object Padding.d N/A 28 4 (loss due to the next object alignment) Instance size: 32 bytes Space losses: 1 bytes internal + 4 bytes external = 5 bytes total
可以看到header后的4個bytes空間分配情況,在所有大小<=Padding Size的類型中,char的優(yōu)先級最高,其次是boolean,
這兩個加起來只有3 bytes(
然后,JVM繼續(xù)分配一個8 bytes大小的空間,最后一個類型object reference(這里是Object)了,在開啟UseCompressedOops的情況下,使用4 bytes的空間,還有4 bytes的空間只能用來對齊了。
子類和父類的field永遠(yuǎn)不會混合在一起,并且父類的field分配完之后才會給子類的field分配空間。
例-4:
public class SuperA { long a; private int b; private float c; private char d; private short e; } public class SubA extends SuperA { private long d; public static void main(String[] args) { System.out.println(ClassLayout.parseClass(SubA.class).toPrintable()); } }
SubA的實例在內(nèi)存中的分布是:
objectsize.SubA object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 12 (object header) N/A 12 4 int SuperA.b N/A 16 8 long SuperA.a N/A 24 4 float SuperA.c N/A 28 2 char SuperA.d N/A 30 2 short SuperA.e N/A 32 8 long SubA.d N/A Instance size: 40 bytes Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
父類SuperA中的field全部分配完后,才分配子類SubA的field。
父類的的最后一個字段與子類的第一個字段以一個Padding Size(4 bytes)來對齊。
例-5:
public class SuperB { private byte a; private int b; } public class SubB extends SuperB { private int a; private long b; public static void main(String[] args) { System.out.println(ClassLayout.parseClass(SubB.class).toPrintable()); } }
SubB的實例在內(nèi)存中分布是:
objectsize.SubB object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 12 (object header) N/A 12 4 int SuperB.b N/A 16 1 byte SuperB.a N/A 17 3 (alignment/padding gap) 20 4 int SubB.a N/A 24 8 long SubB.b N/A Instance size: 32 bytes Space losses: 3 bytes internal + 0 bytes external = 3 bytes total
從offset 16的位置開始看,父類還有最后一個字段a未分配,這時JVM分配一個8 bytes的空間,a占用1 byte,
還有7 bytes未使用,而這7 bytes空間沒有全部用于對齊,也就是說子類字段的分配并不是從offset 24 開始的。
實際上只用了3 bytes空間來對齊(湊夠4 bytes的Padding Size),剩下的4 bytes分配給了子類的a字段。
數(shù)組也是對象,但數(shù)組的header中包含有一個int類型的length值,又多占了4 bytes的空間,所以數(shù)組的header大小是16 bytes。
例-6:
public class ArrayTest { public static void main(String[] args) { System.out.println(ClassLayout.parseInstance(new boolean[1]).toPrintable()); } }
長度為1的boolean數(shù)組的實例在內(nèi)存的分布是:
[Z object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) 05 00 00 f8 (00000101 00000000 00000000 11111000) (-134217723) 12 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) 16 1 boolean [Z.N/A 17 7 (loss due to the next object alignment) Instance size: 24 bytes Space losses: 0 bytes internal + 7 bytes external = 7 bytes total
可以看到,header占用了16 bytes,一個boolean元素占用了1 bytes,剩余7 bytes用于對齊。
參考資料
java-object-memory-structure/
java-object-memory-layout
what-is-the-memory-consumption-of-an-object-in-java)
openjdk-java object layout
java object layout examples
Java Object Size Calculations in 64-bit
mark word & klass pointer
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/70761.html
摘要:筆者多次參與銀行運營商等大型企業(yè)的性能優(yōu)化工作總結(jié)了企業(yè)級應(yīng)用最應(yīng)重視的個性能指標(biāo),主要包括商業(yè)事務(wù),外部服務(wù),垃圾回收以及應(yīng)用布局。應(yīng)用布局最后要探討的性能指標(biāo)是應(yīng)用布局。另一個需要監(jiān)測的是容器性能。 雖然很多人都曾預(yù)言 Java 將一蹶不振,但是不可否認(rèn)的是,很多重要項目中,尤其是銀行和政府一些大型項目,Java 仍在其中扮演著極其重要的角色。筆者多次參與銀行、運營商等大型企業(yè)的性...
摘要:復(fù)制這一工作所花費的時間,在對象存活率達(dá)到一定程度時,將會變的不可忽視。針對老年代老年代的特點是區(qū)域較大,對像存活率高。這種情況,存在大量存活率高的對像,復(fù)制算法明顯變得不合適。 GC(Garbage Collection)即Java垃圾回收機制,是Java與C++的主要區(qū)別之一,作為Java開發(fā)者,一般不需要專門編寫內(nèi)存回收和垃圾清理代碼,對內(nèi)存泄露和溢出的問題,也不需要像C++程序...
摘要:看來還是功力不夠,索性拆成了六篇文章,分別從自動內(nèi)存管理機制類文件結(jié)構(gòu)類加載機制字節(jié)碼執(zhí)行引擎程序編譯與代碼優(yōu)化高效并發(fā)六個方面來做更加細(xì)致的介紹。本文先說說虛擬機的自動內(nèi)存管理機制。在類加載檢查通過后,虛擬機將為新生對象分配內(nèi)存。 歡迎關(guān)注微信公眾號:BaronTalk,獲取更多精彩好文! 書籍真的是常讀常新,古人說「書讀百遍其義自見」還是蠻有道理的。周志明老師的這本《深入理解 Ja...
面試官:今天要不來聊聊JVM調(diào)優(yōu)相關(guān)的吧?面試官:你曾經(jīng)在生產(chǎn)環(huán)境下有過調(diào)優(yōu)JVM的經(jīng)歷嗎?候選者:沒有面試官:...候選者:嗯...是這樣的,我們一般優(yōu)化系統(tǒng)的思路是這樣的候選者:1. 一般來說關(guān)系型數(shù)據(jù)庫是先到瓶頸,首先排查是否為數(shù)據(jù)庫的問題候選者:(這個過程中就需要評估自己建的索引是否合理、是否需要引入分布式緩存、是否需要分庫分表等等)候選者:2. 然后,我們會考慮是否需要擴(kuò)容(橫向和縱向都...
摘要:桌面軟件開發(fā)一直以來是程序員不敢輕易涉足的地方,原因有三丑慢難。打包還有一個人們關(guān)心的方面就是軟件如何打包。這是如今很多軟件的做法。但說到底桌面開發(fā)本身究竟如何我已經(jīng)用做了將近兩年的開發(fā),我覺得已經(jīng)可以滿足桌面開發(fā)的基本需要。 Java FX 桌面軟件開發(fā)一直以來是 Java 程序員不敢輕易涉足的地方,原因有三:丑、慢、難。而自從 Java 8.0 將 JavaFX 包含進(jìn)來之后,情況...
閱讀 2022·2023-04-25 23:30
閱讀 1452·2021-11-24 10:18
閱讀 3070·2021-10-09 09:54
閱讀 2017·2021-10-08 10:05
閱讀 3431·2021-09-23 11:21
閱讀 3161·2019-08-30 15:52
閱讀 1560·2019-08-30 13:05
閱讀 1056·2019-08-30 13:02