2020牛客多校1 I-1 OR 2
可以將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;
在建立執行緒時初始化_handle_area屬性,然後通過handle_area()函式獲取這個屬性的值。
allocate_handle()函式的實現如下:
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原始碼剖析系列文章!