《JVM從小白學成大佬》系列推出到現在,收到了很多小伙伴的好評,也收到了一些小伙伴的建議,在此表示感謝。
有幾個小伙伴提出了希望出一篇介紹對象的創建及訪問,猿人谷向來是沒有原則的,小伙們要求啥,咱就盡力滿足,畢竟文章就是對自己學習的一個總結及和各位小伙伴交流學習的機會。話不多說,直接開擼!
1 創建對象
在Java程序運行過程中無時無刻都有對象被創建出來,java中對象可以采用new或反射或clone或反序列化的方法創建。接下來我們我們介紹在虛擬機中,對象(限於普通Java對象,不包括數組和Class對象等)的創建過程。
字節碼new表示創建對象,虛擬機遇到該指令時,從棧頂取得目標對象在常量池中的索引,接着定位到目標對象的類型。接下來,虛擬機將根據該類的狀態,采取相應的內存分配技術,在內存中分配實例空間,並完成實例數據和對象頭的初始化。這樣,一個對象就在JVM中創建好了。
實例的創建過程,首先根據從類常量池中獲取對象類型信息並驗證類是否已被解析過,若確保該類已被加載和正確解析,使用快速分配(fast allocation)技術為該類分配對象空間;若該類尚未解析過,則只能通過慢速分配(slow allocation)方式分配實例對象。實例的創建流程如下圖所示。
對象創建的基本流程:
- 驗證類已被解析。
- 獲取instanceKlass,確保Klass已完全初始化。
- 若滿足快速分配條件,則進入快速分配流程。
- 若不滿足快速分配條件,或者快速分配失敗,則進入慢速分配流程。
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就是使用第二種方式進行對象訪問的,但從整個軟件開發的范圍來看,各種語言和框架使用句柄來訪問的情況也十分常見。