【JVM從小白學成大佬】6.創(chuàng)建對象及對象的訪問定位
《JVM從小白學成大佬》系列推出到現(xiàn)在,收到了很多小伙伴的好評,也收到了一些小伙伴的建議,在此表示感謝。
有幾個小伙伴提出了希望出一篇介紹對象的創(chuàng)建及訪問,猿人谷向來是沒有原則的,小伙們要求啥,咱就盡力滿足,畢竟文章就是對自己學習的一個總結及和各位小伙伴交流學習的機會。話不多說,直接開擼!
1 創(chuàng)建對象
在Java程序運行過程中無時無刻都有對象被創(chuàng)建出來,java中對象可以采用new或反射或clone或反序列化的方法創(chuàng)建。接下來我們我們介紹在虛擬機中,對象(限于普通Java對象,不包括數(shù)組和Class對象等)的創(chuàng)建過程。
字節(jié)碼new表示創(chuàng)建對象,虛擬機遇到該指令時,從棧頂取得目標對象在常量池中的索引,接著定位到目標對象的類型。接下來,虛擬機將根據該類的狀態(tài),采取相應的內存分配技術,在內存中分配實例空間,并完成實例數(shù)據和對象頭的初始化。這樣,一個對象就在JVM中創(chuàng)建好了。
實例的創(chuàng)建過程,首先根據從類常量池中獲取對象類型信息并驗證類是否已被解析過,若確保該類已被加載和正確解析,使用快速分配(fast allocation)技術為該類分配對象空間;若該類尚未解析過,則只能通過慢速分配(slow allocation)方式分配實例對象。實例的創(chuàng)建流程如下圖所示。

對象創(chuàng)建的基本流程:
- 驗證類已被解析。
- 獲取instanceKlass,確保Klass已完全初始化。
- 若滿足快速分配條件,則進入快速分配流程。
- 若不滿足快速分配條件,或者快速分配失敗,則進入慢速分配流程。
1.1 快速分配
如果在實例分配之前已經完成了類型的解析,那么分配操作僅僅是在內存空間中劃分可用內存,因此能以較高效率實現(xiàn)內存分配,這就是快速分配。
根據分配空間是來自于線程私有區(qū)域還是共享的堆空間,快速分配可以分為兩種空間選擇策略。HotSpot通過線程局部分配緩存技術(Thread-Local Allocation Buffers,即TLABs)可以在線程私有區(qū)域實現(xiàn)空間的分配。
可以通過VM選項UseTLAB來開啟或關閉TLAB功能。
根據是否使用TLAB,快速分配方式有兩種選擇策略:
- 選擇TLAB:首先嘗試在TLAB中分配,因為TLAB是線程私有區(qū)域,故不需要加鎖便能夠確保線程安全。在分配一個新的對象空間時,將首先嘗試在TLAB空間中分配對象空間,若分配空間的請求失敗,則再嘗試使用加鎖機制在Eden區(qū)分配對象。
- 選擇Eden空間:若失敗,則嘗試在共享的Eden區(qū)進行分配,Eden區(qū)是所有線程共享區(qū)域,需要保證線程安全,故采用原子操作進行分配。若分配失敗,則再次嘗試該操作,直到分配成功為止。
實例空間分配成功以后,將對實例進行初始化。待完成對象的空間分配和初始化后,就可以設置棧頂對象引用。當然,對象的空間分配和初始化操作都是基于從類常量池中獲取對象類型并確保該類已被加載和正確解析的前提下進行的,如果類未被解析,則需要進行慢速分配。
1.2 慢速分配
之所以成為慢速分配,正是因為在分配實例前需要對類進行解析,確保類及依賴類已得到正確的解析和初始化。慢速分配是調用InterpreterRuntime模塊_new()進行的,實現(xiàn)代碼如下。
// 確保要初始化的類不是抽象類型
klass->check_valid_for_instantiation(true, CHECK);
// 確保類已初始化
klass->initialize(CHECK);
// 分配實例
oop obj = klass->allocate_instance(CHECK);
// 在線程棧中設置對象引用
thread->set_vm_result(obj);
2 對象的訪問定位
建立對象是為了使用對象,Java程序需要通過棧上的reference數(shù)據來操作堆上的具體對象。由于reference類型在Java虛擬機規(guī)范中只規(guī)定了一個指向對象的引用,并沒有定義這個引用應該通過何種方式去定位、訪問堆中的對象的具體位置,所以對象訪問方式也是取決于虛擬機實現(xiàn)而定的。
目前主流的訪問方式有使用句柄和直接指針兩種:
-
如果使用句柄訪問的話,那么Java堆中將會劃分出一塊內存來作為句柄池,reference中存儲的就是對象的句柄地址,而句柄中包含了對象實例數(shù)據與類型數(shù)據各自的具體地址信息,如下圖所示。

-
如果使用直接指針訪問,那么Java堆對象的布局中就必須考慮如何放置訪問類型數(shù)據的相關信息,而reference中存儲的直接就是對象地址。即使用直接指針訪問在對象被移動時reference本身需要被修改,reference存儲的就是對象地址。如下圖所示。

這兩種對象訪問方式各有優(yōu)勢:
- 使用句柄來訪問的最大好處就是reference中存儲的是穩(wěn)定的句柄地址,在對象被移動(垃圾收集時移動對象時非常普遍的行為)時只會改變句柄中的實例數(shù)據指針,而reference本身不需要修改。
- 使用直接指針訪問方式的最大好處就是速度更快,它節(jié)省了一次指針定位的時間開銷,由于對象的訪問在Java中非常頻繁,因此這類開銷積少成多后也是一項非??捎^的執(zhí)行成本。
HotSpot就是使用第二種方式進行對象訪問的,但從整個軟件開發(fā)的范圍來看,各種語言和框架使用句柄來訪問的情況也十分常見。
微信公眾號:
猿人谷
如果您認為閱讀這篇博客讓您有些收獲,不妨點擊一下右下角的【推薦】
如果您希望與我交流互動,歡迎關注微信公眾號
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接。
浙公網安備 33010602011771號