《JVM G1源码分析和调优》 —2.6.2 句柄
2.6.2 句柄
实际上线程既可以支持Java代码的执行也可以执行本地代码,如果本地代码(这里的本地代码指的是JVM里面的本地代码,而不是用户自定义的本地代码)引用了堆里面的对象该如何处理?是不是也是通过栈?理论上是可行的,实际上JVM并没有区分Java栈和本地方法栈,如果通过栈进行处理则必须要区分这两种情况。JVM设计了另一个概念,handleArea,这是一块线程的资源区,在这个区域分配句柄(handle),并且管理所有的句柄,如果函数还在调用中,那么句柄有效,句柄关联的对象也就是活跃对象。为了管理句柄的生命周期,引入了HandleMark,通常HandleMark分配在栈上,在创建HandleMark的时候标记handleArea对象有效,在HandleMark对象析构的时候,从HandleArea中删除对象的引用。由于所有句柄都形成了一个链表,那么访问这个句柄链表就可以获得本地代码执行中对堆对象的引用。
句柄和OOP对象关联,在HandleArea中有一个slot用于指向OOP对象。
本节源码都在下面两个文件中,为了便于阅读和减少篇幅,我们对其中的类代码进行了重组,代码如下所示:
hotspot/src/share/vm/runtime/handles.cpp
hotspot/src/share/vm/runtime/handles.hpp
class Handle VALUE_OBJ_CLASS_SPEC {
private:
oop* _handle;
……
inline Handle(oop obj) {
if (obj == NULL) {
_handle = NULL;
} else {
_handle = Thread::current()->handle_area()->allocate_handle(obj);
}
}
……
}
在HandleMark中标记Chunk的地址,这个就是找到当前本地方法代码中活跃的句柄,因此也就可以找到对应的活跃的OOP对象。下面是HandleMark的构造函数和析构函数,它们的主要工作就是构建句柄链表,代码如下所示:
class HandleMark {
private:
Thread *_thread; // 这个HandleMark归属的线程
HandleArea *_area; // 保存句柄的区域
Chunk *_chunk; // Chunk 和Area配合,获得准确的内存地址
char *_hwm, *_max; // 句柄区域的属性
size_t _size_in_bytes; // 句柄区域的大小
// HandleMark形成链表的字段
HandleMark* _previous_handle_mark;
HanldeMark(THread* thread)
_thread = thread;
// 获得句柄区域
_area = thread->handle_area();
// 获取handleArea的信息,用于标记句柄分配的状态
_chunk = _area->_chunk;
_hwm = _area->_hwm;
_max = _area->_max;
_size_in_bytes = _area->_size_in_bytes;
// 形成链表,注意HandleMark是通过线程访问,所以这里会关联到线程中
set_previous_handle_mark(thread->last_handle_mark());
thread->set_last_handle_mark(this);
}
HandleMark::~HandleMark() {
HandleArea* area = _area; // 用于编译优化别名分析
// 删除最后加入的chuanks
if( _chunk->next() ) {
// 恢复缓存的信息
_chunk->next_chop();
}
// 恢复handleArea的信息
area->_chunk = _chunk;
area->_hwm = _hwm;
area->_max = _max;
// 删除handlemark
_thread->set_last_handle_mark(previous_handle_mark());
}
……
};
在这里我们提到了Chunk,Chunk的回收是通过前面我们提到的周期性线程Watcher
Thread完成的。
还需要提到一点,就是JVM中的本地代码指的是JVM内部的代码,除了JVM内部的本地代码,还有JNI代码也是本地代码。对于本地代码,并不归JVM直接管理,在执行JNI代码的时候,也有可能访问堆中的OOP对象。所以也需要一个机制进行管理,JVM引入了类似的句柄机制,称为JNIHandle。JNIHandle分为两种,全局和局部对象引用,大部分对象的引用属于局部对象引用,最终还是调用了JNIHandleBlock来管理,因为JNIHandle没有设计一个JNIHandleMark的机制,所以在创建时需要明确调用make_local,在回收时也需要明确调用destory_local。对于全局对象,比如在编译任务compilerTask中会访问Method对象,这时候就需要把这些对象设置为全局的(否则在GC时可能会被回收的)。这两部分在垃圾回收时的处理是不同的,局部JNIhandle是通过线程,全局JNIhandle则是通过全局变量开始。
- 点赞
- 收藏
- 关注作者
评论(0)