第2.8篇-句柄Handle的释放
本篇首先介绍几个与句柄分配与释放密切相关的类,然后重点介绍句柄的释放。
1、HandleArea、Area与Chunk句柄都是在HandleArea中分配并存储的。
HandleArea继承自CHeap,所以是通过malloc()与free()函数在本地内存中分配内存。另外还有ResourceArea,2个都在Thread类中定义,如下:
// Thread local resource area for temporary allocation within the VM ResourceArea* _resource_area; // Thread local handle area for allocation of handles within the VM HandleArea* _handle_area;
在创建Thread实例时,在构造函数中创建HandleArea与ResourceArea实例,如下:
Thread::Thread() { ... set_resource_area(new (mtThread)ResourceArea()); // 初始化_resource_area属性 set_handle_area(new (mtThread) HandleArea(NULL));// 初始化_handle_area属性 ... }
调用重写的new重载运算符为ResourceArea与HandleArea分配内存,如下:
void* Arena::operator new(size_t size, MEMFLAGS flags) throw() { return (void *) AllocateHeap(size, flags|otArena, CALLER_PC); } // 调用malloc()函数分配内存,如果没有足够内存,分配将失败 inline char* AllocateHeap( size_t size, MEMFLAGS flags, address pc = 0, AllocFailType alloc_failmode = AllocFailStrategy::EXIT_OOM ){ char* p = (char*) os::malloc(size, flags, pc); return p; }
现在来看HandleArea类的定义,如下:
源代码位置:openjdk/hotspot/src/share/vm/runtime/handles.hpp class HandleArea: public Arena { ... HandleArea* _prev; // HandleArea通过_prev连接成单链表 public: HandleArea(HandleArea* prev) : Arena(Chunk::tiny_size) { _prev = prev; } private: oop* real_allocate_handle(oop obj) { // 分配内存并存储obj oop* handle = (oop*) Amalloc_4(oopSize); *handle = obj; return handle; } ... };
real_allocate_handle()函数在HandleArea中分配内存并存储obj,此函数调用父类Arena中定义的Amalloc_4()函数分配内存。Arena类的定义如下:
源代码位置:openjdk/hotspot/src/share/vm/memory/allocation.hpp class Arena: public public CHeapObj<mtNone|otArena>{ protected: ... Chunk *_first; // 单链表的第1个Chunk Chunk *_chunk; // 当前正在使用的Chunk char *_hwm, *_max; public: ... void *Amalloc_4(size_t x) { if (_hwm + x > _max) { // 分配新的Chunk块,在新的Chunk块中分配内存 return grow(x); } else { char *old = _hwm; _hwm += x; return old; } } ... };
Amalloc_4()函数会在当前的Chunk块中分配内存,如果当前块的内存不够,调用grow()函数分配新的Chunk块,然后在新的Chunk块中分配内存。
Arena类通过_first、_chunk等属性管理着一个连接成单链表的Chunk,其中_first指向单链表的第一个Chunk,而_chunk指向的是当前可提供内存分配的Chunk,通常为单链表的最后一个块Chunk。_hwm与_max指示当前可分配内存的Chunk的一些分配信息。
Chunk类的定义如下:
源代码位置:openjdk/hotspot/src/share/vm/memory/allocation.hpp class Chunk: CHeapObj<mtChunk> { public: ... Chunk* _next; // 单链表的下一个Chunk size_t _len; // 当前Chunk的大小 char* bottom() const { return ((char*) this) + sizeof(Chunk); } char* top() const { return bottom() + _len; } };
HandleArea与Chunk类之间的关系如图2-17所示。
上图已经清楚的展示了HandleArea与Chunk的关系,灰色部分表示在Chunk中已经分配了的内存,那么新的内存分配就可以从_hwm开始。现在看Amalloc_4()函数的逻辑就非常容易理解了,这个函数还会调用grow()函数分配新的Chunk块,如下:
源代码位置:openjdk/hotspot/src/share/vm/runtime/handles.hpp void* Arena::grow( size_t x ) { size_t len = max(x, Chunk::size); register Chunk *k = _chunk; // Get filled-up chunk address _chunk = new (len) Chunk(len); if( k ) k->_next = _chunk; else _first = _chunk; _hwm = _chunk->bottom(); _max = _chunk->top(); set_size_in_bytes(size_in_bytes() + len); void* result = _hwm; _hwm += x; return result; }
分配新的Chunk块后加入单链表,然后在新的Chunk块中分配x大小的内存。
2、HandleMark
每一个Java线程都有一个私有的句柄区_handle_area来存储其运行过程中的句柄信息,这个句柄区会随着Java线程的栈帧变化。Java线程每调用一个Java方法就会创建一个对应的HandleMark保存创建的对象句柄,然后等调用返回后释放这些对象句柄,此时释放的仅是调用当前方法创建出来的句柄,所以HandleMark只需要恢复到调用方法之前的状态即可。
HandleMark主要用于记录当前线程的HandleArea的内存地址top,当相关的作用域执行完成后,当前作用域之内的HandleMark实例自动销毁,在HandleMark的析构函数中会将HandleArea的当前内存地址到方法调用前的内存地址top之间的所有分配的地址中存储的内容都销毁掉,然后恢复当前线程的HandleArea的内存地址top到方法调用前的状态。
C++的析构函数专门用来释放内存,这绝对是一个需要好好学习的知识点。
HandleMark一般情况下直接在线程栈内存上分配,应该继承自StackObj,但是部分情况下HandleMark也需要在堆内存上分配,所以没有继承自StackObj,并且为了支持在堆内存上分配,重载了new和delete运算符。
类的定义如下:
源代码位置:openjdk/hotspot/src/share/vm/runtime/handles.hpp class HandleMark { private: Thread *_thread; // 拥有当前HandleMark实例的线程 HandleArea *_area; Chunk *_chunk; // Chunk和Area配合,获得准确的内存地址 char *_hwm, *_max;
// 这个值用来保存的HandleArea的总大小,也就是所有的Chunk块的大小的总和 size_t _size_in_bytes; // 通过如下属性让HandleMark形成单链表 HandleMark* _previous_handle_mark; void initialize(Thread* thread); void set_previous_handle_mark(HandleMark* mark) { _previous_handle_mark = mark; } HandleMark* previous_handle_mark() const { return _previous_handle_mark; } size_t size_in_bytes() const { return _size_in_bytes; } public: HandleMark(); HandleMark(Thread* thread) { initialize(thread); } ~HandleMark(); ... };
handleMark也会通过_previous_handle_mark属性形成一条单链表。
在HandleMark的构造函数中会调用initialize()函数,函数的实现如下:
源代码位置:openjdk/hotspot/src/share/vm/runtime/handles.cpp void HandleMark::initialize(Thread* thread) { _thread = thread; _area = thread->handle_area(); _chunk = _area->_chunk; _hwm = _area->_hwm; _max = _area->_max; _size_in_bytes = _area->_size_in_bytes; // 通过当前的HandleMark的_previous_handle_mark属性保存 // 前一个HandleMark实例 HandleMark* hm = thread->last_handle_mark(); set_previous_handle_mark(hm); // 将当前HandleMark实例同线程关联起来 // 注意,线程中的_last_handle_mark属性保存HandleMark实例 thread->set_last_handle_mark(this); }
主要初始化一些属性。Thread中定义的_last_handle_mark属性的定义如下:
源代码位置:openjdk/hotspot/src/share/vm/runtime/thread.hpp // 线程中的此属性永远指定最后一个HandleMark实例 HandleMark* _last_handle_mark;
handleMark的析构函数如下:
源代码位置:openjdk/hotspot/src/share/vm/runtime/handles.cpp HandleMark::~HandleMark() { HandleArea* area = _area; // 如果Chunk的_next属性不为空,说明当前的HandleMark实例创建过新的Chunk块, if( _chunk->next() ) { assert(area->size_in_bytes() > size_in_bytes(), "Sanity check"); // 恢复之前的大小 area->set_size_in_bytes(size_in_bytes()); // 删除当前Chunk以后的所有Chunk,即在当前HandleMark生命周期过程中新创建的Chunk _chunk->next_chop(); } else { // 如果没有下一个Chunk,说明未分配新的Chunk,则area的大小应该保持不变 assert(area->size_in_bytes() == size_in_bytes(), "Sanity check"); } // 恢复HandleArea类中相关的属性到HandleMark构造时的状态 area->_chunk = _chunk; area->_hwm = _hwm; area->_max = _max; // 解除当前HandleMark实例跟i当前线程的关联 _thread->set_last_handle_mark(previous_handle_mark()); }
创建一个新的HandleMark以后,新的HandleMark保存当前线程的area的当前chunk,_hwm ,_max等属性,代码执行期间新创建的Handle实例是在当前线程的area中分配内存,这会导致当前线程的area的当前chunk,_hwm ,_max等属性发生变更,因此代码执行完成后需要将这些属性恢复成之前的状态,并把代码执行过程中新创建的Handle实例的内存给释放掉。
下面看一下对HandleMark的具体使用,LinkResolver::resolve_field()函数中有如下代码片段:
HandleMark hm(THREAD); Handle ref_loader (THREAD, InstanceKlass::cast(current_klass())->class_loader()); Handle sel_loader (THREAD, InstanceKlass::cast(sel_klass())->class_loader());
由于要通过Handle操作ClassLoader对象,所以使用了HandleMark,这个HandleMark的声明应该尽量在第一次使用Handle的地方,这样可尽量延迟使用或避免使用HandleMark,另外还要尽量使用块作用域来缩短其生命周期。