Java對象的內存布局全流程
開始先拋出一個問題:一個對象o,Object o = new Object();創(chuàng)建完成后會占用多少字節(jié)的內存?
要能回答這個問題,就需要了解java對象的內存布局。
對象內存布局
一個Java對象在內存中包括對象頭、實例數據和對齊填充三個部分。如下圖所示:
對象頭
Mark Word
:包含一系列的標記位比如hashcode、GC分代年齡、偏向鎖位,鎖標志位等。這個Mark Word在對象被加了不同量級的鎖時所包含的內容和布局都有所不同,這涉及到鎖升級的知識,暫不展開討論Klass Pointer
:是一個指針,指向描述這個對象類型的元對象,例如Object.class,User.class等
實例數據
instance data
:描述成員變量的信息,如果成員變量是引用類型,那么它就是一個指針。instance data的大小是所有成員變量的占用空間(基本數據類型大小+指針大小)
對齊
padding
:在java中,為了能夠更加高效的利用內存空間,會將對象大小設定為8bytes的整數倍,如果對象頭+實例數據的大小不是8bytes的倍數,那么會在padding區(qū)域填充幾個字節(jié),使得對象占用空間是8bytes的倍數
那么對象布局中各個部分占用內存空間到底多大呢?
對象占用內存空間
由于目前64位操作系統(tǒng)已經基本普及,下面只分析64位操作系統(tǒng)下的情況
指針壓縮
在64位系統(tǒng)中,一個指針占64 bits也就是8 bytes,而在32位系統(tǒng)中指針只占4個字節(jié),于是為了能夠減少內存消耗,從JDK1.6開始,JVM會默認支持指針壓縮,會將指針大小壓縮成4個字節(jié),
這涉及到兩個參數-XX:+UseCompressedOops,-XX:+UseCompressedClassPointers。
UseCompressedOops
:oops: ordinary object pointer,普通對象指針壓縮,例如Object o = new Object();其中o就是個指向new Object()對象的指針,o在指針壓縮前占用8個字節(jié),在指針壓縮后占用4個字節(jié)UseCompressedClassPointers
:壓縮Klass Pointer,壓縮前8個字節(jié),壓縮后4個字節(jié)
對象頭
Mark Word占8個字節(jié)
Klass Pointer
:開啟(默認)壓縮4個字節(jié),不開啟壓縮8個字節(jié)
實例數據
instance data
:根據實際情況計算:如果成員變量是基本數據類型,那么占用空間就是基本數據類型的大小,Java的8大基本數據類型的大小如下:
數據類型 | 占用空間bytes |
---|---|
byte | 1 |
short | 2 |
int | 4 |
long | 8 |
float | 4 |
double | 8 |
char | 2 |
boolean | 1 |
如果成員變量是引用類型,那么就是一個指針大?。ㄩ_啟指針壓縮占4字節(jié),不開啟指針壓縮8字節(jié))
對齊
padding
:如果對象頭+實例數據的大小不是8 bytes的倍數,那么就填充這個區(qū)域,使得對象占用空間能被8個字節(jié)整除(最小情況)
口說無憑,下面將會通過實驗證明
證明對象內存布局
我們需要引用一個依賴:openjdk提供的jol-core:
<dependency> <groupId>org.openjdk.jol</groupId> <artifactId>jol-core</artifactId> <version>0.9</version> </dependency>
1.查看默認情況下沒有成員變量的對象布局
示例代碼:
public class TestObj { public static void main(String[] args) { // 創(chuàng)建對象 Object o = new Object(); // 獲得對象布局內容 String s = ClassLayout.parseInstance(o).toPrintable(); // 打印對象布局 System.out.println(s); } }
輸出結果:
其中對象頭(object header)有三個,前兩個是Mark Word一共8個字節(jié),后面一個是Klass Pointer,占4個字節(jié),由于沒有成員變量,所以實例數據沒有占用空間,而最后4個字節(jié)描述信息為:loss due to the next object alignment,意思就是為了與下一個對象對齊而丟失的部分,也就是對齊填充空間
2.證明Klass Pointer在不開啟壓縮的情況下占用8個字節(jié)
我們只需要在jvm參數上加上-XX:-UseCompressedClassPointers即可,在IDEA工具中可以設置啟動參數:
還是運行上述代碼,運行程序結果:
如上圖所示,對象頭已經占用16個字節(jié),前8個字節(jié)是Mark Word,后8個字節(jié)就是未壓縮的Klass Pointer。我們還注意到對齊填充也沒有了,原因是此時對象占用空間16個字節(jié)已經是8bytes的倍數,所以不需要填充,這完全印證了前面的分析
3.證明實例數據的存在以及大小
示例代碼:
public class TestObj { public static void main(String[] args) { // 創(chuàng)建對象 User user = new User(1, "zhangsan"); // 獲得對象布局內容 String s = ClassLayout.parseInstance(user).toPrintable(); // 打印對象布局 System.out.println(s); } } class User { private int id; private String name; public User(int id, String name) { this.id = id; this.name = name; } }
打印結果:
如上圖所示,int類型的id占用4個字節(jié),指向字符串對象的name指針占用4個字節(jié),加上對齊,對象一共占用24 bytes
4.最后驗證不開啟指針壓縮的情況下指針占用8 bytes
只需在jvm參數上加上-XX:-UseCompressedOops:
還是運行上面的代碼,打印結果:
很顯然,此時name指針已經占用了8個字節(jié)
一般來說,UseCompressedClassPointers和UseCompressedOops是默認開啟的,我們無需關心也無需修改。但是有個隱藏的細節(jié)就是:UseCompressedClassPointers的開啟依賴UseCompressedOops的開啟,并且開啟UseCompressedOops 也默認強制開啟UseCompressedClassPointers,關閉UseCompressedOops 默認關閉UseCompressedClassPointers。
至此,關于java對象的內存布局已經有了一個基本的了解,那么文章一開始的問題現在應該能很輕松的回答:16個字節(jié)。
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
Okhttp在SpringBoot中的應用實戰(zhàn)記錄(太強了)
這篇文章主要給大家介紹了關于Okhttp在SpringBoot中應用實戰(zhàn)的相關資料,在Spring Boot中使用OkHttp主要是為了發(fā)送HTTP請求和處理響應,OkHttp是一個高效、易用的HTTP客戶端庫,它具有簡潔的API和強大的功能,需要的朋友可以參考下2023-12-12一篇文章帶你使用SpringBoot基于WebSocket的在線群聊實現
這篇文章主要介紹了一篇文章帶你使用SpringBoot基于WebSocket的在線群聊實現,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-10-10spring data jpa開啟批量插入、批量更新的問題解析
這篇文章主要介紹了spring data jpa開啟批量插入、批量更新問題,本文通過圖文實例相結合給大家介紹的非常詳細,需要的朋友可以參考下2021-07-07深入學習java并發(fā)包ConcurrentHashMap源碼
這篇文章主要介紹了深入學習java并發(fā)包ConcurrentHashMap源碼,整個 ConcurrentHashMap 由一個個 Segment 組成,Segment 代表”部分“或”一段“的意思,所以很多地方都會將其描述為分段鎖。,需要的朋友可以參考下2019-06-06