作者 | 哪兒來的moon 責編 | 張文
頭圖 | CSDN 下載自視覺中國
來源 | 哪兒來的moon(ID:onetraveller_llxz)
面試的時候,要是面試官問了標題那種問題,你知道怎麼解答嗎?
本文內容將針對以上面試問題做出解答。
對象的創建過程
以下內容基於 HotSpot VM 分代模型。
這張圖其實就能完整的說明一個對象的創建過程到底發生了什麼。從左上角開始,一步一步來:
一個對象 new 出來先判斷線程棧是否能分配下;
如果能分配下,直接分配在棧中。
如果分配不下則進行第二步。
判斷該對象是否足夠大;
如果足夠大,則直接進入老年代。
如果不夠大,則進行第三步。
判斷創建對象的線程的 TLAB(本地線程緩衝區)空間是否足夠;
如果足夠,直接分配在 TLAB 中。
如果不夠,則進入 Eden 區中其他空間。然後進行第四步。
GC 清除;
如果清除掉了該對象,則直接結束。
如果沒有清除掉對象,進行第5步。
此刻對象進入 Survivor 1 區,判斷年齡是否足夠大;
如果年齡足夠大,則直接進入 old 區域。
如果年齡不夠大,則進入 Survivor 2 區,然後進入第4步,循環往復。
通過這張流程圖的步驟解析之後,大家應該對一個對象的創建過程有一個相對清晰的思路了。但其實會有很多小細節容易被忽略,這也就是為什麼 jvm 會在對象的創建過程中不遺餘力的解釋,細分多種情況。
為了讓大家更深入地理解,我們再來看看下面幾個問題:
為什麼對象會選擇先分配在棧中?
首先,棧是線程私有的,將對象優先分配在棧中,可以通過 pop 直接將對象的所有信息、空間直接清除,當線程消亡的時候也可以直接清理這一塊的 TLAB 區域。
為什麼 jvm 會讓大對象會直接進入老年代?
大對象需要連續的空間來存儲,如果不存入老年代,對 jvm 來說就是一個負擔;倘若沒有足夠的空間就有可能導致提前觸發 gc 來清理空間來安置大對象。
為什麼會選擇先進入 TLAB?
TLAB 是線程本地緩衝區,TLAB 的好處就是防止不同線程創建對象選擇同一塊兒內存區域而產生競爭,大大降低其競爭概率。
為什麼會有兩個 Survivor 區?並且存活且年齡不夠大的對象會從一個Survivor 區轉到另一個 Survivor 區?
根據根可達算法,jvm 會尋找到所有正在使用的對象,沒有使用的就是垃圾。通常來說,大部分對象都是用完就拋棄的,所以真正在 Survivor 區長時間存活的對象非常少,將這部分對象從一個 Survivor 區轉到另一個 Survivor 區後,就可以直接對這個 Survivor 區進行全量的空間回收,效率會很高。
對象的內存布局
回到文章標題,Object o = new Object();到底佔用多少個字節?這道題的目的其實就是考驗看你對對象的內存布局了解的是否清晰。先上圖:
在 java 中對象的內存布局分為兩種情況:非數組對象和數組對象,數組對象和非數組對象的區別就是它需要額外的空間存儲數組的長度length。
對象頭
對象頭又分為 MarkWord 和 Class Pointer 兩部分。
MarkWord:包含一系列的標記位,比如輕量級鎖的標記位、偏向鎖標記位、gc記錄信息等等。在32位系統佔4位元組,在64位系統中佔8位元組。
ClassPointer:用來指向對象對應的Class對象(其對應的元數據對象)的內存地址。在32位系統佔4位元組,在64位系統中佔8位元組。
Length:只在數組對象中存在,用來記錄數組的長度,佔用4位元組。
Interface data
Interface data:對象實際數據。對象實際數據包括了對象的所有成員變量,其大小由各個成員變量的大小決定。(這裡不包括靜態成員變量,因為它是在方法區維護的)
Padding
Padding:Java 對象佔用空間是 8 字節對齊的,即所有 Java 對象佔用 bytes 數必須是 8 的倍數。這是因為當我們從磁碟中取一個數據時,不會說我想取一個字節就是一個字節,都是按照一塊兒一塊兒來取的。這一塊大小是 8 個字節,所以為了完整,padding 的作用就是補充字節,保證對象是 8 字節的整數倍。
小貼士:moon 在上文特意標註了 32 位系統和 64 位系統不同區域佔用空間大小的區別,是因為對象指針在 64 位 JVM 下的尋址更長,所以相比 32 位會多出來更多佔用空間。
現在假設一個場景,公司現在項目部署的機器是 32 位的, 老闆要讓你將項目遷移到 64 位的系統上,但是 64 位系統比 32 位系統需要更多佔用空間,應該怎麼辦?
正常來說我們是不需要這一部分多餘空間的,因為 jvm 已經幫你考慮好了,那就是指針壓縮。
指針壓縮
-XX:+UseCompressedOops 這個參數就是 JVM 提供的解決方案。通過壓縮指針,佔用的空間就會被壓縮為原來的一半,節約空間。classpointer 參數大小就受到其影響。
那麼Object o = new Object()到底佔用多少個字節?
通過剛才內存布局的學習後,這個問題就很好回答了。面試官其實就是想問你對象的內存布局的理解是怎樣的。
我們這裡就針對這個問題的結果分析下,這裡分兩種情況:
在開啟指針壓縮的情況下,markword 佔用 8 字節,classpoint 佔用 4 字節,Interface data 無數據,總共是12位元組,由於對象需要為8的整數倍,Padding 會補充4個字節,總共佔用16位元組的存儲空間。
在沒有指針壓縮的情況下,markword 佔用 8 字節,classpoint 佔用 8位元組,Interface data 無數據,總共是16位元組。
了解了對象的創建過程和對象的內存布局,Object o = new Object() 佔用了多少字節?這類問題就不是難事了!