可以將Handle理解成訪問對象的一個“句柄”。垃圾回收時對象可能被移動(對象地址發生改變),通過Handle訪問對象可以對使用者屏蔽垃圾回收細節。
Handle涉及到的相關類的繼承關系如下圖所示。
HotSpot會通過Handle對Oop和某些Klass進行操作。下圖左邊顯示了直接訪問的情況,下圖右邊顯示了間接訪問的情況。
可以看到,當對Oop直接引用時,如果Oop的地址發生變化,那么所有的引用都要更新,如圖有3處引用,所以都需要更新;當通過Handle對Oop間接引用時,如果Oop的地址發生變化,那么只需要更新Handle中保存的對Oop的引用即可。
每個Oop都有一個對應的Handle,這樣通過對應的Handle可直接獲取對應的Oop,不需要進行類型轉換。為了讀者方便閱讀,這里再次給出了Oop繼承體系,如下圖所示。
可以看到Handle繼承體系與Oop繼承體系類似,實際上也有相應的對應關系,例如通過instanceHandle操作instanceOopDesc,通過objArrayHandle操作objArrayOopDesc。
與Oop類似,Klass也需要通過Handle來間接引用。如下幾個Klass有對應的Handle:
Klass -klassHandle InstanceKlass - instanceKlassHandle ConstantPool - constantPoolHandle Method - methodHandle
現在假設有個Person類,還有這個類的一個Person對象,那么可以像下圖這樣理解Handle、Oop與Klass之間的關系:
下面具體看一下Handle的定義,如下:
// Base class for all handles. Provides overloading of frequently // used operators for ease of use. class Handle VALUE_OBJ_CLASS_SPEC { private: oop* _handle; // 可以看到是對oop的封裝 protected: oop obj() const { return _handle == NULL ? (oop)NULL : *_handle; } oop non_null_obj() const { assert(_handle != NULL, "resolving NULL handle"); return *_handle; } ... }
調用obj()或non_null_obj()方法獲取被封裝的oop對象,不過並不會直接調用Handle對象的obj()或non_null_obj()對象,而是通過C++的運算符重載來獲取。Handle類重載了()和->運算符,如下:
// General access oop operator () () const { return obj(); } oop operator -> () const { return non_null_obj(); }
可以這樣使用:
oop obj = ...; Handle h1(obj); // allocate new handle oop obj1 = h1(); // get handle value h1->print(); // invoking operation on oop
由於重載了運算符(),所以h1()會調用()運算符的重載方法,重載方法中調用obj()獲取到被封裝的oop對象。重載了運算符->,所以h1->print()同樣會調用oop對象的print()方法。
另外還需要知道,Handle分配在本地線程的HandleArea中,這樣在進行垃圾回收時,只需要掃描每個線程的HandleArea即可找出句柄引用的活躍對象。
每次創建句柄對象時,都會調用到Handle類的構造函數,其中一個構造函數如下:
inline Handle::Handle(oop obj) { if (obj == NULL) { _handle = NULL; } else { HandleArea* ha = Thread::current()->handle_area(); _handle = ha->allocate_handle(obj); } }
參數obj就是要通過句柄操作的對象。通過調用當前線程的handle_area()函數獲取HandleArea,然后調用allocate_handle()在HandleArea中分配存儲obj的空間並將obj保存起來。
每個線程都會有一個_handle_area屬性,定義如下:
// Thread local handle area for allocation of handles within the VM HandleArea* _handle_area; // 定義在Thread類中
在創建線程時初始化_handle_area屬性,然后通過handle_area()函數獲取這個屬性的值。
allocate_handle()函數為對象obj分配一個新的句柄,實現如下:
oop* real_allocate_handle(oop obj) { oop* handle = (oop*) Amalloc_4(oopSize); *handle = obj; return handle; }
分配空間並完成obj的存儲操作。
句柄的釋放要通過HandleMark來完成,不過在介紹HandleMark之前需要介紹一下FHandleArea、Area及Chunk等類的實現,下一篇會詳細分析。
相關文章的鏈接如下:
1、在Ubuntu 16.04上編譯OpenJDK8的源代碼
關注公眾號,有HotSpot源碼剖析系列文章!