HotSpot虛擬機對象探秘:對象的創建,我居然看懂了!
引言
大家好,我是你們的老朋友小米,一個每天都在代碼與咖啡之間切換狀態的 31 歲程序員。
今天這篇文章,我想帶你一起走進 Java 世界中一個特別“魔幻”的角落——對象創建。
這個話題你肯定不陌生,對吧?我們平時寫代碼:
就這一句,貌似平平無奇,但你有沒有想過,JVM 背后都悄咪咪做了些什么?對象是怎么一步一步,從“無中生有”被變出來的?HotSpot 是怎么分配內存、初始化、搞定 header 的?
今天,小米就帶你走進 HotSpot 的幕后舞臺,一探對象創建的秘密。
一切從 new 開始,但 new 只是個“開關”
那天我正和同事阿剛喝著下午茶,聊著最近項目優化的事,他突然拋來一句:
“你知道 JVM 是怎么創建一個 Java 對象的嗎?”
當時我一愣,心里想著不就 new 一下嘛,還能有啥。
但等他掏出一張紙開始畫圖的時候,我才知道,我懂得還遠遠不夠。
其實當我們寫下 new User() 這句代碼時,Java 編譯器做的只是把它編譯成了一條字節碼指令:new。
而真正的魔術,是在 HotSpot 虛擬機執行字節碼時才開始的。
這就好比你按下了電梯按鈕,電梯是不是真的來,還得看調度系統是不是給你安排得當。
HotSpot對象創建的五個階段,就像造房子
阿剛給我講了一個特別形象的比喻,他說 JVM 創建對象就像造房子,大致可以拆解為這五個步驟:
- 類加載檢查(確定圖紙是否有)
- 內存分配(拿地皮)
- 內存初始化(打地基)
- 設置對象頭(裝修主體)
- 執行構造方法(軟裝上線)
我當時猛點頭,這不就是咱寫代碼時那句 new 背后的隱藏劇本嗎!
下面我就一個個來講,保證你聽完之后也能在面試官面前吹爆 JVM 對象創建流程。
第一步:類加載檢查,圖紙有沒有?
每次 new 一個對象,JVM 的第一反應是:這個類我加載過沒?
HotSpot 會通過類加載子系統去檢查類的元數據是否已經加載、初始化過。
如果沒有?那就先通過雙親委派模型把這個類加載進來。別忘了,類的元信息是在 方法區(或說元空間 Metaspace) 里的。
沒有圖紙,房子肯定不能造,對吧。
第二步:內存分配,拿地皮
這個階段,HotSpot 要為新對象劃一塊地盤,也就是在堆內存中“圈一塊地”。
它有兩種分配方式,分別叫做:
- 指針碰撞(bump the pointer)
- 空閑列表(free list)
如果堆內存是規整的,也就是已經整理過、不會有內存碎片,JVM 就直接通過一個叫 alloc_ptr 的指針往前一推,一口氣分配下一塊內存,效率超高。
如果堆是不規整的(比如用了 CMS 垃圾收集器),那 JVM 就得在內存中找一個合適的空閑塊,效率會稍慢。
但這還不夠,JVM 還得保證線程安全——畢竟多線程下好幾個線程都可能在同時 new 對象啊。
于是,它用上了三板斧:
- 通過加鎖(如 TLAB 鎖)
- 通過原子指令 CAS + 重試
- 使用線程本地分配緩沖(TLAB)
大多數時候,我們的對象是在 TLAB(Thread Local Allocation Buffer) 中分配的,線程獨占,不用加鎖,效率拉滿。
第三步:內存初始化,打地基
拿到了地皮之后,HotSpot 下一步就是“清地基”——也就是把剛分配的內存區域清零。
這一步很關鍵,如果不清零,那對象的字段可能全是垃圾值,后面出 bug 可就一發不可收拾了。
清零完畢后,JVM 知道這塊內存已經是“干凈的”,可以用來創建對象了。
第四步:設置對象頭,裝修主體
HotSpot 中的每個對象,都不是裸奔的,它的前面都帶著一個“對象頭”。
這個對象頭包含了兩類信息:
- Mark Word(標記字段):用于存儲對象的哈希碼、GC 分代年齡、鎖信息等等。
- Klass Pointer:指向對象所屬的類的元數據。
JVM 會把這些信息填充進去,就像把房子主體的水電管線都布好。
這一步結束,對象就有了“身份”和“結構”。
第五步:執行構造方法,軟裝上線
最后一步才是我們 Java 程序員最熟悉的一步:
圖片
JVM 會根據類的構造函數,逐一執行初始化代碼,比如字段賦值、初始化列表等。
這一步,就是給房子裝上窗簾、沙發、燈具——真正成為一個“能住人”的家。
“new”背后的思考:為什么你要懂這些?
聽完阿剛那天的分享,我感觸特別深:
我們平時太容易把對象創建當成“理所當然”,卻沒意識到背后是一套精密的設計和優化。
尤其在高并發、內存敏感的業務場景下,理解對象創建過程能幫你:
- 識別性能瓶頸(比如頻繁 GC 的根源可能是對象創建太多)
- 更高效地使用 TLAB 與逃逸分析
- 編寫更貼合 JVM 優化路徑的代碼
比如:用對象池復用對象、避免創建臨時對象、使用基本類型替代包裝類型等等。
對象創建的“高級玩法”:逃逸分析與棧上分配
其實 HotSpot 還會進一步優化對象的創建,比如當 JVM 發現某個對象 沒有逃出當前方法 時,它甚至會直接在棧上分配對象,而不是堆中。
這叫做:逃逸分析(Escape Analysis)
如果逃逸分析判斷對象“只在方法內部用”,那它就不用堆內存了,直接在棧中分配,效率更高,GC 也不會管它。
你想啊,這對象一用完方法就銷毀了,壓根不用等 GC。
還有一種優化方式叫做:標量替換,JVM 會把整個對象拆成若干個字段來優化分配,用得更靈活。
這些優化,都得益于你理解對象是怎么創建的,才能在實際項目中利用起來。
總結
說到這,你會發現,對象的創建遠比你想象中復雜,但每一個步驟都是為了性能、并發、安全做出妥協和平衡。
讓我再用建房子的比喻總結一次對象創建的五大步驟:
- 看圖紙(類加載檢查)
- 拿地皮(內存分配)
- 打地基(內存清零)
- 裝修主體(設置對象頭)
- 上軟裝(執行構造函數)
如果你能理解這些流程,相信在調優性能、理解內存問題、寫出更高質量代碼時,你會有意想不到的收獲。
最后,給你一個小問題
在 JVM 中,如果你創建了 100 萬個小對象,它們都短生命周期、只在方法里用,你會如何優化它?