本篇首先介绍几个与句柄分配与释放密切相关的类,然后重点介绍句柄的释放。
1、HandleArea、Area与Chunk
句柄都是在HandleArea中分配并存储的,类的定义如下:
// Thread local handle area class HandleArea: public Arena { friend class HandleMark; ... HandleArea* _prev; // HandleArea通过_prev连接成单链表 public: // Constructor HandleArea(HandleArea* prev) : Arena(Chunk::tiny_size) { _prev = prev; } // Handle allocation 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()函数。HandleArea的父类Arena的定义如下:
// Fast allocation of memory class Arena: public CHeapObj { protected: ... Chunk *_first; // First chunk Chunk *_chunk; // current chunk char *_hwm, *_max; // High water mark and max in current chunk void* grow(size_t x); // Get a new Chunk of at least size x size_t _size_in_bytes; // Size of arena (used for memory usage tracing) public: Arena(); Arena(size_t init_size); Arena(Arena *old); ~Arena() { _first->chop(); } char* hwm() const { return _hwm; } // Fast allocate in the arena. Common case is: pointer test + increment. // Further assume size is padded out to words // Warning: in LP64, Amalloc_4 is really Amalloc_8 void *Amalloc_4(size_t x) { // 保证在64位上,x是一个字的整倍数 assert( (x&(sizeof(char*)-1)) == 0, "misaligned size" ); if (_hwm + x > _max) { return grow(x); } else { char *old = _hwm; _hwm += x; return old; } } ... };
Amalloc_4()函数会在当前的Chunk块中分配内存,如果当前块的内存不够,则会调用grow()方法分配新的Chunk块,然后在新的Chunk块中分配内存。
这个类通过_first、_chunk等管理着一个连接成单链表的Chunk,其中 _first指向单链表的第一个Chunk,而_chunk指向的是当前可提供内存分配的Chunk,通常为单链表的最后一个块Chunk。_hwm与_max指示当前可分配内存的Chunk的一些分配信息。
Chunk类的定义如下:
// Linked list of raw memory chunks class Chunk: public CHeapObj { public: ... Chunk* _next; // Next Chunk in list size_t _len; // Size of this Chunk // Boundaries of data area (possibly unused) char* bottom() const { return ((char*) this) + sizeof(Chunk); } char* top() const { return bottom() + _len; } };
HandleArea与Chunk类之间的关系如下图所示。
2、HandleMark
每一个Java线程都有一个私有的句柄区_handle_area来存储其运行过程中句柄信息,这个句柄区是随着Java线程的栈帧变化的。Java线程每调用一个Java方法就会创建一个对应HandleMark来保存已经的对象句柄,然后等调用返回后恢复。
HandleMark主要用于记录当前线程的HandleArea的内存地址top,当相关的作用域执行完成后,当前作用域之内的HandleMark实例自动销毁,在HandleMark的析构函数中会将HandleArea的当前内存地址到方法调用前的内存地址top之间的所有分配的地址中存储的内容都销毁掉,然后恢复当前线程的HandleArea的内存地址top到方法调用前的状态。
C++的析构函数专门用来释放内存,这绝对是一个需要好好学习的知识点。
HandleMark一般情况下直接在线程栈内存上分配,应该继承自StackObj,但是部分情况下HandleMark也需要在堆内存上分配,所以没有继承自StackObj,并且为了支持在堆内存上分配,重载了new和delete方法。
类的定义如下:
class HandleMark { private: Thread *_thread; // thread that owns this mark HandleArea *_area; // saved handle area Chunk *_chunk; // saved arena chunk,Chunk和Area配合,获得准确的内存地址 char *_hwm, *_max; // saved arena info size_t _size_in_bytes; // size of handle area // Link to previous active HandleMark in thread HandleMark* _previous_handle_mark; void initialize(Thread* thread); // common code for constructors 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(); // see handles_inline.hpp HandleMark(Thread* thread) { initialize(thread); } ~HandleMark(); ... };
handleMark也会通过_previous_handle_mark属性形成一条单链表。
在HandleMark的构造方法中会调用initialize()方法,方法的实现如下:
void HandleMark::initialize(Thread* thread) { _thread = thread; // Save area _area = thread->handle_area(); // Save current top _chunk = _area->_chunk; _hwm = _area->_hwm; _max = _area->_max; _size_in_bytes = _area->_size_in_bytes; // Link this in the thread // 将当前HandleMark实例同线程关联起来 HandleMark* hm = thread->last_handle_mark(); set_previous_handle_mark(hm); thread->set_last_handle_mark(this); // 注意,线程中的_last_handle_mark属性来保存HandleMark对象 }
方法主要初始化一些属性。Thread中定义的_last_handle_mark属性的定义如下:
// Point to the last handle mark HandleMark* _last_handle_mark;
handleMark的析构函数如下:
HandleMark::~HandleMark() { HandleArea* area = _area; // help compilers with poor alias analysis // Delete later chunks if( _chunk->next() ) { // reset arena size before delete chunks. Otherwise, the total // arena size could exceed total chunk size assert(area->size_in_bytes() > size_in_bytes(), "Sanity check"); area->set_size_in_bytes(size_in_bytes()); // 删除当前Chunk以后的所有Chunk,即在方法调用期间新创建的Chunk _chunk->next_chop(); } else { // 如果没有下一个Chunk,说明未分配新的Chunk,则area的大小应该保持不变 assert(area->size_in_bytes() == size_in_bytes(), "Sanity check"); } // Roll back arena to saved top markers // 恢复area的属性到HandleMark构造时的状态 area->_chunk = _chunk; area->_hwm = _hwm; area->_max = _max; // Unlink this from the thread // 解除当前HandleMark跟线程的关联 _thread->set_last_handle_mark(previous_handle_mark()); }
创建一个新的HandleMark以后,新的HandleMark保存当前线程的area的当前chunk,_hwm ,_max等属性,代码执行期间新创建的Handle实例是在当前线程的area中分配内存,这会导致当前线程的area的当前chunk,_hwm ,_max等属性发生变更,因此代码执行完成后需要将这些属性恢复成之前的状态,并把代码执行过程中新创建的Handle实例的内存给释放掉。
相关文章的链接如下:
1、在Ubuntu 16.04上编译OpenJDK8的源代码
关注公众号,有HotSpot源码剖析系列文章!