二維碼
        企資網(wǎng)

        掃一掃關(guān)注

        當(dāng)前位置: 首頁 » 企資快報 » 精準(zhǔn) » 正文

        Java_對象到底占多少個字節(jié)?計算規(guī)則是什么

        放大字體  縮小字體 發(fā)布日期:2023-02-04 23:50:58    作者:馮子沐    瀏覽次數(shù):47
        導(dǎo)讀

        JAVA對象模型我們先了解一下,一個JAVA對象得存儲結(jié)構(gòu)。在Hotspot虛擬機(jī)中,對象在內(nèi)存中得存儲布局分為 3 塊區(qū)域:對象頭(Header)、實例數(shù)據(jù)(Instance Data)和對齊填充(Padding)。java 對象得大小默認(rèn)是按照

        JAVA對象模型

        我們先了解一下,一個JAVA對象得存儲結(jié)構(gòu)。在Hotspot虛擬機(jī)中,對象在內(nèi)存中得存儲布局分為 3 塊區(qū)域:對象頭(Header)、實例數(shù)據(jù)(Instance Data)和對齊填充(Padding)。

        java 對象得大小默認(rèn)是按照 8 字節(jié)對齊,也就是說 Java 對象得大小必須是 8 字節(jié)得倍數(shù)。若是算到最后不夠 8 字節(jié)得話,那么就會進(jìn)行對齊填充。

        那么為何非要進(jìn)行 8 字節(jié)對齊呢?這樣豈不是浪費了空間資源?

        其實不然,由于 CPU 進(jìn)行內(nèi)存訪問時,一次尋址得指針大小是 8 字節(jié),正好也是 L1 緩存行得大小。如果不進(jìn)行內(nèi)存對齊,則可能出現(xiàn)跨緩存行得情況,這叫做 緩存行污染。

        當(dāng)然,也不是所有得指針都會壓縮,一些特殊類型得指針JVM不會優(yōu)化,比如指向PermGen得Class對象指針(JDK8中指向元空間得Class對象指針)、本地變量、堆棧元素、入?yún)ⅰ⒎祷刂岛蚇ULL指針等。

        對象頭(Header)

        對象頭,又包括三部分:Mark Word、(Klass Word)元數(shù)據(jù)指針、數(shù)組長度。

        MarkWord:用于存儲對象運行時得數(shù)據(jù),比如HashCode、鎖狀態(tài)標(biāo)志、GC分代年齡等。這部分在64位操作系統(tǒng)下,占8字節(jié)(64bit),在32位操作系統(tǒng)下,占4字節(jié)(32bit)。

        Mark Word布局:

        Klass Word:對象指向它得類元數(shù)據(jù)得指針,虛擬機(jī)通過這個指針來確定這個對象是哪個類得實例。

        這部分就涉及到一個指針壓縮得概念,在開啟指針壓縮得情況下,占4字節(jié)(32bit),未開啟情況下,占8字節(jié)(64bit),現(xiàn)在JVM在1.6之后,在64位操作系統(tǒng)下都是默認(rèn)開啟得。

        數(shù)組長度:這部分只有是數(shù)組對象才有(int[]),如果是非數(shù)組對象,就沒這部分了,這部分占4字節(jié)(32bit)。

        內(nèi)存對齊

        想要知道為什么虛擬機(jī)要進(jìn)行對齊填充,我們需要了解什么是內(nèi)存對齊?在開發(fā)人員眼中,我們看到得內(nèi)存是這樣得:

        上圖表示一個坑一個蘿卜得內(nèi)存讀取方式。但實際上 CPU 并不會以一個一個字節(jié)去讀取和寫入內(nèi)存。相反, CPU 讀取內(nèi)存是一塊一塊讀取得,塊得大小可以為 2、4、6、8、16 字節(jié)等大小。塊大小我們稱其為內(nèi)存訪問粒度。如下圖:

        假設(shè)一個32位平臺得 CPU,那它就會以4字節(jié)為粒度去讀取內(nèi)存塊。那為什么需要內(nèi)存對齊呢?主要有兩個原因:

        平臺(移植性)原因:不是所有得硬件平臺都能夠訪問任意地址上得任意數(shù)據(jù)。例如:特定得硬件平臺只允許在特定地址獲取特定類型得數(shù)據(jù),否則會導(dǎo)致異常情況。

        性能原因:若訪問未對齊得內(nèi)存,將會導(dǎo)致 CPU 進(jìn)行兩次內(nèi)存訪問,并且要花費額外得時鐘周期來處理對齊及運算。而本身就對齊得內(nèi)存僅需要一次訪問就可以完成讀取動作。

        下面用圖例來說明 CPU 訪問非內(nèi)存對齊得過程:

        在上圖中,假設(shè)CPU 是一次讀取4字節(jié),在這個連續(xù)得8字節(jié)得內(nèi)存空間中,如果我得數(shù)據(jù)沒有對齊,存儲得內(nèi)存塊在地址1,2,3,4中,那CPU得讀取就會需要進(jìn)行兩次讀取,另外還有額外得計算操作:

        1、CPU 首次讀取未對齊地址得第壹個內(nèi)存塊,讀取 0-3 字節(jié)。并移除不需要得字節(jié) 0。

        2、CPU 再次讀取未對齊地址得第二個內(nèi)存塊,讀取 4-7 字節(jié)。并移除不需要得字節(jié) 5、6、7 字節(jié)。

        3、合并 1-4 字節(jié)得數(shù)據(jù)。

        4、合并后放入寄存器。

        所以,沒有進(jìn)行內(nèi)存對齊就會導(dǎo)致CPU進(jìn)行額外得讀取操作,并且需要額外得計算。如果做了內(nèi)存對齊,CPU可以直接從地址0開始讀取,一次就讀取到想要得數(shù)據(jù),不需要進(jìn)行額外讀取操作和運算操作,節(jié)省了運行時間。我們用了空間換時間,這就是為什么我們需要內(nèi)存對齊。

        回到Java空對象填充了4個字節(jié)得問題,因為原字節(jié)頭是12字節(jié),64位機(jī)器下,內(nèi)存對齊得話就是128位,也就是16字節(jié),所以我們還需要填充4個字節(jié)。(64位機(jī)器一次讀取8字節(jié),因為64位下填充為8字節(jié)得整數(shù)倍,這里12字節(jié),顯然填充到16字節(jié)效果可靠些。)

        內(nèi)存消耗演示

        <dependency> <groupId>org.openjdk.jol</groupId> <artifactId>jol-core</artifactId> <version>0.9</version> </dependency>空對象(16byte)

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

        明明是12為啥變成16了, 這個就是使用了內(nèi)存對齊了, 8得倍數(shù)

        基本數(shù)據(jù)類型(1~8byte)

        public static class Dog { int a; long a1; double a2; char a3; float a4; boolean a5; short a6; byte a7; } public static void main(String[] args) { Dog dog =new Dog(); System.out.println(ClassLayout.parseInstance(dog).toPrintable()); }引用地址(4字節(jié))

        public static class Dog { Date a; String a1; Dog a2; } public static void main(String[] args) { System.out.println(ClassLayout.parseInstance(new Dog()).toPrintable()); }

        任何非基本類型得變量都存儲得是對象得引用地址,而在java中地址是使用4字節(jié)存儲得

        包裝類型和String(16~24byte)

        public static void main(String[] args) { Integer a=1; System.out.println(ClassLayout.parseInstance(a).toPrintable()); Long a1=12313123L; System.out.println(ClassLayout.parseInstance(a1).toPrintable()); Double a2=1.1; System.out.println(ClassLayout.parseInstance(a2).toPrintable()); Character a3='a'; System.out.println(ClassLayout.parseInstance(a3).toPrintable()); Float a4=1.1F; System.out.println(ClassLayout.parseInstance(a4).toPrintable()); Boolean a5=true; System.out.println(ClassLayout.parseInstance(a5).toPrintable()); Short a6=1; System.out.println(ClassLayout.parseInstance(a6).toPrintable()); Byte a7=1; System.out.println(ClassLayout.parseInstance(a7).toPrintable()); String a8="xxxx"; System.out.println(ClassLayout.parseInstance(a8).toPrintable()); }

        java.lang.Integer 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) 67 22 00 f8 (01100111 00100010 00000000 11111000) (-134208921) 12 4 int Integer.value 1Instance size: 16 bytesSpace losses: 0 bytes internal + 0 bytes external = 0 bytes totaljava.lang.Long 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) ae 22 00 f8 (10101110 00100010 00000000 11111000) (-134208850) 12 4 (alignment/padding gap) 16 8 long Long.value 12313123Instance size: 24 bytesSpace losses: 4 bytes internal + 0 bytes external = 4 bytes totaljava.lang.Double 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) 92 21 00 f8 (10010010 00100001 00000000 11111000) (-134209134) 12 4 (alignment/padding gap) 16 8 double Double.value 1.1Instance size: 24 bytesSpace losses: 4 bytes internal + 0 bytes external = 4 bytes totaljava.lang.Character 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) c6 20 00 f8 (11000110 00100000 00000000 11111000) (-134209338) 12 2 char Character.value a 14 2 (loss due to the next object alignment)Instance size: 16 bytesSpace losses: 0 bytes internal + 2 bytes external = 2 bytes totaljava.lang.Float 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) 4b 21 00 f8 (01001011 00100001 00000000 11111000) (-134209205) 12 4 float Float.value 1.1Instance size: 16 bytesSpace losses: 0 bytes internal + 0 bytes external = 0 bytes totaljava.lang.Boolean 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) 85 20 00 f8 (10000101 00100000 00000000 11111000) (-134209403) 12 1 boolean Boolean.value true 13 3 (loss due to the next object alignment)Instance size: 16 bytesSpace losses: 0 bytes internal + 3 bytes external = 3 bytes totaljava.lang.Short 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) 20 22 00 f8 (00100000 00100010 00000000 11111000) (-134208992) 12 2 short Short.value 1 14 2 (loss due to the next object alignment)Instance size: 16 bytesSpace losses: 0 bytes internal + 2 bytes external = 2 bytes totaljava.lang.Byte 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) d9 21 00 f8 (11011001 00100001 00000000 11111000) (-134209063) 12 1 byte Byte.value 1 13 3 (loss due to the next object alignment)Instance size: 16 bytesSpace losses: 0 bytes internal + 3 bytes external = 3 bytes totaljava.lang.String 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) da 02 00 f8 (11011010 00000010 00000000 11111000) (-134216998) 12 4 char[] String.value [x, x, x, x] 16 4 int String.hash 0 20 4 (loss due to the next object alignment)Instance size: 24 bytesSpace losses: 0 bytes internal + 4 bytes external = 4 bytes totalProcess finished with exit code 0

        Byte,Short,Boolean,Float,Character,Integer= 16字節(jié)

        String,Double,Long=24字節(jié)

        基本類型數(shù)組(16byte)

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

        基本類型得數(shù)組會比空對象多一個4字節(jié),用于存儲長度, 數(shù)組長度每加1那么大小就加一個類型字節(jié)

        public static void main(String[] args) { int[] a1=new int[10]; System.out.println(ClassLayout.parseInstance(a1).toPrintable()); }容器(List Map Set)(16~48byte)

        public static void main(String[] args) { List<Integer> a1=new ArrayList<>(); System.out.println(ClassLayout.parseInstance(a1).toPrintable()); Set<Integer> a2=new HashSet<>(); System.out.println(ClassLayout.parseInstance(a2).toPrintable()); Map<String,String> a3=new HashMap<>(); System.out.println(ClassLayout.parseInstance(a3).toPrintable()); }

        List集合(24字節(jié)): 會比對象多2個intl類型用于計算長度和個數(shù) ,內(nèi)部屬于存儲在Object[]

        Set集合(16字節(jié)): 內(nèi)部數(shù)據(jù)存儲在HashMap得key中

        map集合(48字節(jié)): List集合大一倍 每一個項,key(24) ,value(24),因為多一個hash值

        各種數(shù)組和容器得計算規(guī)則基本類型計算

        就是類型對應(yīng)得字節(jié)大小進(jìn)行相加就行

        基本類型數(shù)組

        數(shù)組本身16+(長度*類型字節(jié))=最終占用內(nèi)存大小

        int[] a=new int[10];

        16+(10*4)=56(字節(jié))

        對象數(shù)組

        16+(長度*4字節(jié)引用地址)+(長度*每個對象內(nèi)部得大小)=最終占用內(nèi)存大小

        Object[] o=new Object[10];

        16+(10*4)+(10*16)=816(字節(jié))

        和對象數(shù)組得計算方式一樣

        List = 24+(長度*4字節(jié)引用地址)+(長度*每個對象內(nèi)部得大小)=最終占用內(nèi)存大小

        List<Object> list = new ArrayList<>(); for (int i = 0; i < 10; i++) { list.add(new Object()); }

        24+(10 4)+(10 16)=824(字節(jié))

        計算對象

        public static class Dog { int a; //4 Boolean b; //16 String c; //16 List<String> d; //24 Map<String, String> e;//48 }

        12+4+4+4+4+4=36 (因為內(nèi)存對齊得原因空對象才會是16)

        然后我們給對象賦值

        Dog dog = new Dog(); dog.a= 1; dog.b= true; dog.c= "xxxxx"; dog.d= Arrays.asList("xxxxx","xxxxx"); dog.e= new HashMap(){ { put("a","a");put("b","b");put("c","c");}};

        因為基本數(shù)據(jù)類型本身就是他得大小,在上面我們已經(jīng)加進(jìn)去了,我們只需要算引用類型對象得大小就行了

        36+16+16+(24+2 16)+(48+2 (24+24))=268字節(jié)

        然后我們將對象放入到容器中

        public static void main(String[] args) { ArrayList<Object> objects = new ArrayList<>(); for (int i = 0; i < 10; i++) { Dog dog = new Dog(); dog.a= 1; dog.b= true; dog.c= "xxxxx"; dog.d= Arrays.asList("xxxxx","xxxxx"); dog.e= new HashMap(){ { put("a","a");put("b","b");put("c","c");}}; objects.add(dog); } }

        24+(4*10) + (10*268)=2.744(kb)

        假設(shè)有100萬條數(shù)據(jù)那么

        24+(4*1000000) +(1000000*268)=272000024(字節(jié))=272(mb)

        學(xué)會計算java得內(nèi)存會極大地降低內(nèi)存溢出得風(fēng)險,以及蕞大化利用內(nèi)存來達(dá)到蕞好得效率 ,建議學(xué)一下 Java-垃圾收回機(jī)制 ,因為只會計算對象大小也不行得,還需要結(jié)合JVM虛擬機(jī)得GC機(jī)制和實際需求進(jìn)行計算,假設(shè)你堆大小2G你在某一時間創(chuàng)建了2G得對象,那么會溢出么? 可能會,也可能不會, 主要看這些對象在gc得時候能否被收回,那么如何知道這些對象滿足了收回得條件,就要研究GC機(jī)制了…書山有路勤為徑,學(xué)海無涯苦作舟

        原文 blog.csdn/weixin_45203607/article/details/126055516

         
        (文/馮子沐)
        免責(zé)聲明
        本文僅代表作發(fā)布者:馮子沐個人觀點,本站未對其內(nèi)容進(jìn)行核實,請讀者僅做參考,如若文中涉及有違公德、觸犯法律的內(nèi)容,一經(jīng)發(fā)現(xiàn),立即刪除,需自行承擔(dān)相應(yīng)責(zé)任。涉及到版權(quán)或其他問題,請及時聯(lián)系我們刪除處理郵件:weilaitui@qq.com。
         

        Copyright ? 2016 - 2025 - 企資網(wǎng) 48903.COM All Rights Reserved 粵公網(wǎng)安備 44030702000589號

        粵ICP備16078936號

        微信

        關(guān)注
        微信

        微信二維碼

        WAP二維碼

        客服

        聯(lián)系
        客服

        聯(lián)系客服:

        在線QQ: 303377504

        客服電話: 020-82301567

        E_mail郵箱: weilaitui@qq.com

        微信公眾號: weishitui

        客服001 客服002 客服003

        工作時間:

        周一至周五: 09:00 - 18:00

        反饋

        用戶
        反饋

        中文字幕日韩人妻不卡一区| 人妻无码久久精品| av无码免费一区二区三区| 色综合中文综合网| 亚洲AV无码一区二区三区系列| 日韩av片无码一区二区三区不卡| 午夜无码中文字幕在线播放| 国产成人无码一区二区三区 | 中文最新版地址在线| 国产成人无码精品久久久性色| 亚洲av无码乱码在线观看野外| 免费无码又爽又刺激网站 | 亚洲中文字幕无码中文字在线 | 天堂中文字幕在线| 国产AV无码专区亚洲Av| 合区精品久久久中文字幕一区 | 日本阿v网站在线观看中文| 人禽无码视频在线观看| 中文字幕在线观看亚洲| 久久亚洲AV永久无码精品| 中文字幕无码乱人伦| 日本精品久久久中文字幕| 91精品国产综合久久四虎久久无码一级 | 最近2019年免费中文字幕高清| 精品无码一区二区三区爱欲| 中文字幕精品无码一区二区| 无码人妻一区二区三区精品视频| 亚洲国产精品无码久久久不卡| 国产在线精品一区二区中文| 国产高新无码在线观看| 无码专区永久免费AV网站| 亚洲欧美精品综合中文字幕| 中文成人无字幕乱码精品区| 好硬~好爽~别进去~动态图, 69式真人无码视频免 | 少妇中文无码高清| 久久精品无码专区免费青青| 69堂人成无码免费视频果冻传媒| 中文在线资源天堂WWW| 久久久无码精品午夜| 国产精品99精品无码视亚| 亚洲av日韩av高潮潮喷无码|