1. 程式人生 > >【JVM從小白學成大佬】6.建立物件及物件的訪問定位

【JVM從小白學成大佬】6.建立物件及物件的訪問定位

《JVM從小白學成大佬》系列推出到現在,收到了很多小夥伴的好評,也收到了一些小夥伴的建議,在此表示感謝。

有幾個小夥伴提出了希望出一篇介紹物件的建立及訪問,猿人谷向來是沒有原則的,小夥們要求啥,咱就盡力滿足,畢竟文章就是對自己學習的一個總結及和各位小夥伴交流學習的機會。話不多說,直接開擼!

1 建立物件

在Java程式執行過程中無時無刻都有物件被創建出來,java中物件可以採用new或反射或clone或反序列化的方法建立。接下來我們我們介紹在虛擬機器中,物件(限於普通Java物件,不包括陣列和Class物件等)的建立過程。

位元組碼new表示建立物件,虛擬機器遇到該指令時,從棧頂取得目標物件在常量池中的索引,接著定位到目標物件的型別。接下來,虛擬機器將根據該類的狀態,採取相應的記憶體分配技術,在記憶體中分配例項空間,並完成例項資料和物件頭的初始化。這樣,一個物件就在JVM中建立好了。

例項的建立過程,首先根據從類常量池中獲取物件型別資訊並驗證類是否已被解析過,若確保該類已被載入和正確解析,使用快速分配(fast allocation)技術為該類分配物件空間;若該類尚未解析過,則只能通過慢速分配(slow allocation)方式分配例項物件。例項的建立流程如下圖所示。

物件建立的基本流程:

  1. 驗證類已被解析。
  2. 獲取instanceKlass,確保Klass已完全初始化。
  3. 若滿足快速分配條件,則進入快速分配流程。
  4. 若不滿足快速分配條件,或者快速分配失敗,則進入慢速分配流程。

1.1 快速分配

如果在例項分配之前已經完成了型別的解析,那麼分配操作僅僅是在記憶體空間中劃分可用記憶體,因此能以較高效率實現記憶體分配,這就是快速分配。

根據分配空間是來自於執行緒私有區域還是共享的堆空間,快速分配可以分為兩種空間選擇策略。HotSpot通過執行緒區域性分配快取技術(Thread-Local Allocation Buffers,即TLABs)可以線上程私有區域實現空間的分配。

可以通過VM選項UseTLAB來開啟或關閉TLAB功能。

根據是否使用TLAB,快速分配方式有兩種選擇策略:

  • 選擇TLAB:首先嚐試在TLAB中分配,因為TLAB是執行緒私有區域,故不需要加鎖便能夠確保執行緒安全。在分配一個新的物件空間時,將首先嚐試在TLAB空間中分配物件空間,若分配空間的請求失敗,則再嘗試使用加鎖機制在Eden區分配物件。
  • 選擇Eden空間:若失敗,則嘗試在共享的Eden區進行分配,Eden區是所有執行緒共享區域,需要保證執行緒安全,故採用原子操作進行分配。若分配失敗,則再次嘗試該操作,直到分配成功為止。

例項空間分配成功以後,將對例項進行初始化。待完成物件的空間分配和初始化後,就可以設定棧頂物件引用。當然,物件的空間分配和初始化操作都是基於從類常量池中獲取物件型別並確保該類已被載入和正確解析的前提下進行的,如果類未被解析,則需要進行慢速分配。

1.2 慢速分配

之所以成為慢速分配,正是因為在分配例項前需要對類進行解析,確保類及依賴類已得到正確的解析和初始化。慢速分配是呼叫InterpreterRuntime模組_new()進行的,實現程式碼如下。

// 確保要初始化的類不是抽象型別
klass->check_valid_for_instantiation(true, CHECK);
// 確保類已初始化
klass->initialize(CHECK);
// 分配例項
oop obj = klass->allocate_instance(CHECK);
// 線上程棧中設定物件引用
thread->set_vm_result(obj);

2 物件的訪問定位

建立物件是為了使用物件,Java程式需要通過棧上的reference資料來操作堆上的具體物件。由於reference型別在Java虛擬機器規範中只規定了一個指向物件的引用,並沒有定義這個引用應該通過何種方式去定位、訪問堆中的物件的具體位置,所以物件訪問方式也是取決於虛擬機器實現而定的。

目前主流的訪問方式有使用控制代碼和直接指標兩種:

  • 如果使用控制代碼訪問的話,那麼Java堆中將會劃分出一塊記憶體來作為控制代碼池,reference中儲存的就是物件的控制代碼地址,而控制代碼中包含了物件例項資料與型別資料各自的具體地址資訊,如下圖所示。

  • 如果使用直接指標訪問,那麼Java堆物件的佈局中就必須考慮如何放置訪問型別資料的相關資訊,而reference中儲存的直接就是物件地址。即使用直接指標訪問在物件被移動時reference本身需要被修改,reference儲存的就是物件地址。如下圖所示。

這兩種物件訪問方式各有優勢:

  • 使用控制代碼來訪問的最大好處就是reference中儲存的是穩定的控制代碼地址,在物件被移動(垃圾收集時移動物件時非常普遍的行為)時只會改變控制代碼中的例項資料指標,而reference本身不需要修改。
  • 使用直接指標訪問方式的最大好處就是速度更快,它節省了一次指標定位的時間開銷,由於物件的訪問在Java中非常頻繁,因此這類開銷積少成多後也是一項非常可觀的執行成本。

HotSpot就是使用第二種方式進行物件訪問的,但從整個軟體開發的範圍來看,各種語言和框架使用控制代碼來訪問的情況也十分常見