1. 程式人生 > 實用技巧 >loj2985「WC2019」I 君的商店(二分,思維)

loj2985「WC2019」I 君的商店(二分,思維)

本篇首先介紹幾個與控制程式碼分配與釋放密切相關的類,然後重點介紹控制程式碼的釋放。

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的原始碼

2、除錯HotSpot原始碼

3、HotSpot專案結構 

4、HotSpot的啟動過程

5、HotSpot二分模型(1)

6、HotSpot的類模型(2)

7、HotSpot的類模型(3)

8、HotSpot的類模型(4)

9、HotSpot的物件模型(5)

10、HotSpot的物件模型(6)

11、操作控制程式碼Handle(7)

關注公眾號,有HotSpot原始碼剖析系列文章!