可以将Handle理解成访问对象的一个“句柄”。垃圾回收时对象可能被移动(对象地址发生改变),通过Handle访问对象可以对使用者屏蔽垃圾回收细节,还能降低垃圾收集器查找GC Roots的复杂度,提高GC回收的效率。
Handle涉及到的相关类的继承关系如下图所示。
HotSpot VM会通过Handle对oop和某些Klass进行操作。下图左边显示了直接访问的情况,下图右边显示了间接访问的情况。
可以看到,当对oop直接引用时,如果oop的地址发生变化,那么所有的引用都要更新,左图有3处引用都需要更新;当通过Handle对oop间接引用时,如果oop的地址发生变化,那么只需要更新Handle中保存的对oop的引用即可。
每个oop都有一个对应的Handle,为了读者方便阅读,这里再次给出了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的定义,如下:
源代码位置:openjdk/hotspot/src/share/vm/runtime/handles.hpp 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; } ... }
如上类定义了唯一一个属性_handle,这个属性是一个指向oop的指针,所以可以看作只是一次简单的对oop的封装而已。当获取oop时,只能通过_handle来操作。
获取被封装的oop实例(一个指向oop的指针_handle)时,并不会直接调用obj()或non_null_obj()函数获取,而是通过C++的运算符重载来获取。Handle类重载了()和->运算符,如下:
源代码位置:openjdk/hotspot/src/share/vm/runtime/handles.hpp 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()会调用operator ()运算符的重载函数,重载函数中调用obj()函数获取被封装的oop对象。h1->print()同样会通过operator ->运算符的重载函数调用oop对象的print()函数。
另外还需要知道,Handle分配在本地线程的HandleArea中,这样在进行垃圾回收时,只需要扫描每个线程的HandleArea即可找出所有句柄,进而找出所有被线程引用的活跃对象。
每次创建句柄时,都会调用到Handle类的构造函数,其中一个构造函数如下:
源代码位置:openjdk/hotspot/src/share/vm/runtime/handles.inline.hpp inline Handle::Handle(oop obj) { if (obj == NULL) { _handle = NULL; } else { _handle = Thread::current()->handle_area()->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分配一个新的句柄,实现如下:
源代码位置:openjdk/hotspot/src/share/vm/runtime/handles.hpp oop* allocate_handle(oop obj) { return real_allocate_handle(obj); } oop* real_allocate_handle(oop obj) { oop* handle = (oop*) Amalloc_4(oopSize); *handle = obj; return handle; }
分配空间并存储obj。
句柄的释放要通过HandleMark来完成,不过在介绍HandleMark之前需要介绍一下HandleArea、Area及Chunk等类的实现,下一节将会详细分析。
原文链接: https://www.cnblogs.com/mazhimazhi/p/13297034.html
欢迎关注
微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍;
也有高质量的技术群,里面有嵌入式、搜广推等BAT大佬
原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/366202
非原创文章文中已经注明原地址,如有侵权,联系删除
关注公众号【高性能架构探索】,第一时间获取最新文章
转载文章受原作者版权保护。转载请注明原作者出处!