兩個詞可以概括整個gc流程:
回收型別:
首先我們來看erl的進程結構(erlang_process.h)中,關於記憶體管理的一些欄位:
Eterm* htop; /* Heap top */ 堆頂
Eterm* stop; /* Stack top */ 棧頂
Eterm* heap; /* Heap start */ 堆起始地址
Eterm* hend; /* Heap end */ 堆結束地址
那麼疑問來了,怎麼沒有棧區的起始地址和結束地址?在process的標頭檔案的宏定義中我們可以找到:
# define HEAP_START(p) (p)->heap
# define HEAP_TOP(p) (p)->htop
# define HEAP_END(p) (p)->hend
# define STACK_START(p) (p)->hend
# define STACK_TOP(p) (p)->stop
# define STACK_END(p) (p)->htop
我們的疑問便得到瞭解答: erl進程空間裡堆區和棧區是記憶體中的同一塊區域,區別在堆的空間從下往上增長,棧的空間從上往下增長。
在erl程式啓動時,會呼叫erts_init_gc函數進行gc相關的一些初始化,其主要邏輯是初始化heap_sizes陣列,該陣列的作用是預先計算進程需要申請的記憶體大小。
eg:heap_sizes[9] = 1598, heap_sizes[10] = 2586, 如果進程當前需要空間大小爲2500,那麼系統會申請的空間大小爲2586,而不是2500; 若使用process_flag(min_heap_size, 2500)設定進程的最小堆空間爲2500,實際上系統設定的值是2586
heap_sizes[0] = 12;
heap_sizes[1] = 38;
for(i = 2; i < 23; i++) {
heap_sizes[i] = heap_sizes[i-1] + heap_sizes[i-2] + 1;
}
for (i = 23; i < ALENGTH(heap_sizes); i++) {
heap_sizes[i] = heap_sizes[i-1] + heap_sizes[i-1]/5;
}
heap_sizes陣列前23個值按照斐波拉契數列增長,之後按照百分之20的增長率增長。
下面 下麪爲小回收的實現核心分析
static int minor_collection(Process* p, ...)
{
if (MAX_HEAP_SIZE_GET(p)) {
判斷是否設定max_heap_size,設定且超出設定,則return -2;
外部呼叫邏輯會對minor_collection的返回值進行判斷,爲-2時會幹掉當前進程
}
if (OLD_HEAP(p) == NULL && mature_size != 0) {
沒有年老堆的話,建立之
}
// 判斷是否有年老堆以及年老堆的剩餘空間是否足夠
if (OLD_HEAP(p) &&
((mature_size <= OLD_HEND(p) - OLD_HTOP(p)) &&
((BIN_OLD_VHEAP_SZ(p) > BIN_OLD_VHEAP(p))) ) ) {
stack_size = p->hend - p->stop; // 使用的棧記憶體
new_sz = stack_size + size_before; // size_before 爲young_gen_usage函數計算出的 堆使用記憶體 + msg佔用的進程外部記憶體
new_sz = next_heap_size(p, new_sz, 0); // 從heap_sizes陣列中找到適合的記憶體空間數值
prev_old_htop = p->old_htop;
// 申請new_sz大小的記憶體,並轉移數據
do_minor(p, live_hf_end, (char *) mature, mature_size*sizeof(Eterm), new_sz, objv, nobj);
// message_queue_data 設定爲 on_heap時需要把在外部的msg轉移到進程的堆空間中
if (p->sig_qs.flags & FS_ON_HEAP_MSGQ)
move_msgs_to_heap(p);
new_mature = p->old_htop - prev_old_htop;
// size_after爲gc後剩下的數據大小
size_after = new_mature;
size_after += HEAP_TOP(p) - HEAP_START(p) + p->mbuf_sz;
// 增加記錄的小回收次數
GEN_GCS(p)++;
// need_after 小回收後年輕堆中數據佔用的空間
need_after = ((HEAP_TOP(p) - HEAP_START(p)) + need + stack_size);
adjust_size = 0;
// 調整年輕堆的大小, 擴大縮小或保持不變 調整規則從判斷條件就可以梳理出來,此處不多做描述
if ((HEAP_SIZE(p) > 3000) && (4 * need_after < HEAP_SIZE(p)) &&
((HEAP_SIZE(p) > 8000) || (HEAP_SIZE(p) > (OLD_HEND(p) - OLD_HEAP(p))))) {
Uint wanted = 3 * need_after;
Uint old_heap_sz = OLD_HEND(p) - OLD_HEAP(p);
if (wanted*9 < old_heap_sz) {
Uint new_wanted = old_heap_sz / 8;
if (new_wanted > wanted) {
wanted = new_wanted;
}
}
wanted = wanted < MIN_HEAP_SIZE(p) ? MIN_HEAP_SIZE(p) : next_heap_size(p, wanted, 0);
if (wanted < HEAP_SIZE(p)) {
// 縮小堆空間
shrink_new_heap(p, wanted, objv, nobj);
adjust_size = p->htop - p->heap;
}
}
else if (need_after > HEAP_SIZE(p)) {
// 增加堆空間
grow_new_heap(p, next_heap_size(p, need_after, 0), objv, nobj);
adjust_size = p->htop - p->heap;
}
// 計算消耗的reduction值
return gc_cost(size_after, adjust_size);
}
// 沒有足夠的空間進行小回收,則當前函數return-1, 外層呼叫會判斷並執行大回收
return -1;
}
大回收實現核心分析
static int major_collection(Process* p, ,,,)
{
size_before = ygen_usage;
size_before += p->old_htop - p->old_heap; //年老堆中使用的記憶體
stack_size = p->hend - p->stop; //棧使用的記憶體
new_sz = stack_size + size_before; //計算總的使用空間
new_sz = next_heap_size(p, new_sz, 0); //從heap_sizes陣列中找到適合的記憶體空間數值
if (MAX_HEAP_SIZE_GET(p)) {
判斷是否設定max_heap_size,設定且超出設定,則return -2;
外部呼叫邏輯會對minor_collection的返回值進行判斷,爲-2時會幹掉當前進程
}
n_htop = n_heap = (Eterm *) ERTS_HEAP_ALLOC(ERTS_ALC_T_HEAP, sizeof(Eterm)*new_sz); //按new_sz申請空間
n_htop = full_sweep_heaps(p, live_hf_end, 0, n_heap, n_htop, oh, oh_size, objv, nobj); //移動堆數據
//移動棧數據
stk_sz = HEAP_END(p) - p->stop;
sys_memcpy(n_heap + new_sz - stk_sz, p->stop, stk_sz * sizeof(Eterm));
p->stop = n_heap + new_sz - stk_sz;
//釋放舊空間
erts_deallocate_young_generation(p);
HEAP_START(p) = n_heap;
HEAP_TOP(p) = n_htop;
HEAP_SIZE(p) = new_sz;
HEAP_END(p) = n_heap + new_sz;
GEN_GCS(p) = 0; //小gc計數清0
HIGH_WATER(p) = HEAP_TOP(p); //設定水位線,下一次gc,水位線下仍然存活的數據將被移動到年老堆
if (p->sig_qs.flags & FS_ON_HEAP_MSGQ)
move_msgs_to_heap(p); //若設定了on_heap,則將存在進程外的msg移動到進程堆中
size_after = HEAP_TOP(p) - HEAP_START(p) + p->mbuf_sz;
adjusted = adjust_after_fullsweep(p, need, objv, nobj); //下文分析
//計算使用的reduction
return gc_cost(size_after, adjusted ? size_after : 0);
}
adjust_after_fullsweep函數解析:
static int adjust_after_fullsweep(Process *p, ...)
{
int adjusted = 0;
Uint wanted, sz, need_after;
Uint stack_size = STACK_SZ_ON_HEAP(p);
need_after = (HEAP_TOP(p) - HEAP_START(p)) + need + stack_size;
//need_after 可以理解爲當前需要的空間大小
if (HEAP_SIZE(p) < need_after) {
// 剩餘空間不滿足need_after的需求,則堆需要增長。增長的期望值爲當前的需求值
sz = next_heap_size(p, need_after, 0);
grow_new_heap(p, sz, objv, nobj);
adjusted = 1;
} else if (3 * HEAP_SIZE(p) < 4 * need_after){
//當前使用的空間超過當前分配堆空間的百分之75,則標記下次gc時需要增長空間
FLAGS(p) |= F_HEAP_GROW;
} else if (4 * need_after < HEAP_SIZE(p) && HEAP_SIZE(p) > H_MIN_SIZE){
//當前使用的空間小於當前分配的空間的百分之25,則縮小
//縮小的期望值爲兩倍當前的需求值
wanted = 2 * need_after;
sz = wanted < p->min_heap_size ? p->min_heap_size : next_heap_size(p, wanted, 0);
if (sz < HEAP_SIZE(p)) {
shrink_new_heap(p, sz, objv, nobj);
adjusted = 1;
}
}
return adjusted;
}
最後看看grow_new_heap是如何增長堆區空間的, shrink_new_heap類似就不多做分析
static void grow_new_heap(Process *p, Uint new_sz, Eterm* objv, int nobj)
{
Eterm* new_heap;
Uint heap_size = HEAP_TOP(p) - HEAP_START(p); //堆區大小
Uint stack_size = p->hend - p->stop; //棧區大小
Sint offs;
new_heap = (Eterm *) ERTS_HEAP_REALLOC(ERTS_ALC_T_HEAP,
(void *) HEAP_START(p),
sizeof(Eterm)*(HEAP_SIZE(p)), //這個參數沒啥用
sizeof(Eterm)*new_sz);
//申請從地址HEAP_START(p)開始,大小爲sizeof(Eterm)*new_sz的連續記憶體空間
//如果HEAP_START(p)地址後的空間不夠,則會分配新的記憶體,並將當前空間的數據複製到新記憶體,並自動釋放掉舊記憶體
if ((offs = new_heap - HEAP_START(p)) == 0) { //原地擴充空間
HEAP_END(p) = new_heap + new_sz;
sys_memmove(p->hend - stack_size, p->stop, stack_size * sizeof(Eterm)); //調整棧區位置
p->stop = p->hend - stack_size;
} else { //分配了新空間, 即HEAP_START(p)發生了變化,則需要調整相關指針的指向
char* area = (char *) HEAP_START(p);
Uint area_size = (char *) HEAP_TOP(p) - area;
Eterm* prev_stop = p->stop;
offset_heap(new_heap, heap_size, offs, area, area_size);
HIGH_WATER(p) = new_heap + (HIGH_WATER(p) - HEAP_START(p)); //調整水位線的指向
HEAP_END(p) = new_heap + new_sz;
prev_stop = new_heap + (p->stop - p->heap);
p->stop = p->hend - stack_size;
sys_memmove(p->stop, prev_stop, stack_size * sizeof(Eterm)); //調整棧區位置
offset_rootset(p, offs, area, area_size, objv, nobj);
HEAP_TOP(p) = new_heap + heap_size;
HEAP_START(p) = new_heap;
}
HEAP_SIZE(p) = new_sz;
}