句柄Handle的釋放(8)
- 2020 年 7 月 15 日
- 筆記
本篇首先介紹幾個與句柄分配與釋放密切相關的類,然後重點介紹句柄的釋放。
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源碼剖析系列文章!