1 回答

TA貢獻(xiàn)1946條經(jīng)驗(yàn) 獲得超4個(gè)贊
golang是一門自帶垃圾回收的語(yǔ)言,它的內(nèi)存分配器和tmalloc(thread-caching malloc)很像,大多數(shù)情況下是不需要用戶自己管理內(nèi)存的。最近了解了一下golang內(nèi)存管理,寫出來(lái)分享一下,不正確的地方請(qǐng)大佬們指出。
1.內(nèi)存池:
應(yīng)該有一個(gè)主要管理內(nèi)存分配的部分,向系統(tǒng)申請(qǐng)大塊內(nèi)存,然后進(jìn)行管理和分配。
2.垃圾回收:
當(dāng)分配的內(nèi)存使用完之后,不直接歸還給系統(tǒng),而是歸還給內(nèi)存池,方便進(jìn)行下一次復(fù)用。至于垃圾回收選擇標(biāo)記回收,還是分代回收算法應(yīng)該符合語(yǔ)言設(shè)計(jì)初衷吧。
3.大小切分:
使用單獨(dú)的數(shù)組或者鏈表,把需要申請(qǐng)的內(nèi)存大小向上取整,直接從這個(gè)數(shù)組或鏈表拿出對(duì)應(yīng)的大小內(nèi)存塊,方便分配內(nèi)存。大的對(duì)象以頁(yè)申請(qǐng)內(nèi)存,小的對(duì)象以塊來(lái)申請(qǐng),避免內(nèi)存碎片,提高內(nèi)存使用率。
4.多線程管理:
每個(gè)線程應(yīng)該有自己的內(nèi)存塊,這樣避免同時(shí)訪問(wèn)共享區(qū)的時(shí)候加鎖,提升語(yǔ)言的并發(fā)性,線程之間通信使用消息隊(duì)列的形式,一定不要使用共享內(nèi)存的方式。提供全局性的分配鏈,如果線程內(nèi)存不夠用了,可向分配鏈申請(qǐng)內(nèi)存。
這樣的內(nèi)存分配設(shè)計(jì)涵蓋了大部分語(yǔ)言的,上面的想法其實(shí)是把golang語(yǔ)言內(nèi)存分配抽象出來(lái)。其實(shí)Java語(yǔ)言也可以以同樣的方式理解。內(nèi)存池就是JVM堆,主要負(fù)責(zé)申請(qǐng)大塊內(nèi)存;多線程管理方面是使用棧內(nèi)存,每個(gè)線程有自己獨(dú)立的棧內(nèi)存進(jìn)行管理。
golang內(nèi)存分配器
golang內(nèi)存分配器主要包含三個(gè)數(shù)據(jù)結(jié)構(gòu):MHeap,MCentral以及MCache
1.MHeap:分配堆,主要是負(fù)責(zé)向系統(tǒng)申請(qǐng)大塊的內(nèi)存,為下層MCentral和MCache提供內(nèi)存服務(wù)。他管理的基本單位是MSpan(若干連續(xù)內(nèi)存頁(yè)的數(shù)據(jù)結(jié)構(gòu))
type MSpan struct
{
MSpan *next;
MSpan *prev;
PageId start; // 開(kāi)始的頁(yè)號(hào)
uintptr npages; // 頁(yè)數(shù)
…..
};
可以看出MSpan是一個(gè)雙端鏈表的形式,里面存儲(chǔ)了它的一些位置信息。
通過(guò)一個(gè)基地址+(頁(yè)號(hào)*頁(yè)大小),就可以定位到這個(gè)MSpan的實(shí)際內(nèi)存空間。
type MHeap struct
{
lock mutex;
free [_MaxMHeapList] mSpanList // free lists of given length
freelarge mSpanList // free lists length >= _MaxMHeapList
busy [_MaxMHeapList] mSpanList // busy lists of large objects of given length
busylarge mSpanList
};
free數(shù)組以span為序號(hào)管理多個(gè)鏈表。當(dāng)central需要時(shí),只需從free找到頁(yè)數(shù)合適的鏈表。large鏈表用于保存所有超出free和busy頁(yè)數(shù)限制的MSpan。
MHeap示意圖:
2.MCache:運(yùn)行時(shí)分配池,不針對(duì)全局,而是每個(gè)線程都有自己的局部?jī)?nèi)存緩存MCache,他是實(shí)現(xiàn)goroutine高并發(fā)的重要因素,因?yàn)榉峙湫?duì)象可直接從MCache中分配,不用加鎖,提升了并發(fā)效率。
type MCache struct
{
tiny byte*; // Allocator cache for tiny objects w/o pointers.
tinysize uintptr;
alloc[NumSizeClasses] MSpan*; // spans to allocate from
};
盡可能將微小對(duì)象組合到一個(gè)tiny塊中,提高性能。
alloc[]用于分配對(duì)象,如果沒(méi)有了,則可以向?qū)?yīng)的MCentral獲取新的Span進(jìn)行操作。
線程中分配小對(duì)象(16~32K)的過(guò)程:
對(duì)于 size 介于 16 ~ 32K byte 的內(nèi)存分配先計(jì)算應(yīng)該分配的 sizeclass,然后去 mcache 里面 alloc[sizeclass] 申請(qǐng),如果 mcache.alloc[sizeclass] 不足以申請(qǐng),則 mcache 向 mcentral 申請(qǐng)mcentral 給 mcache 分配完之后會(huì)判斷自己需不需要擴(kuò)充,如果需要?jiǎng)t想 mheap 申請(qǐng)。
每個(gè)線程內(nèi)申請(qǐng)內(nèi)存是逐級(jí)向上的,首先看MCache是否有足夠空間,沒(méi)有就像MCentral申請(qǐng),再?zèng)]有就像MHeap,MHeap向系統(tǒng)申請(qǐng)內(nèi)存空間。
3.MCentral:作為MHeap和MCache的承上啟下的連接。承上,從MHeap申請(qǐng)MSpan;啟下,將MSpan劃分為各種尺寸的對(duì)象提供給MCache使用。
type MCentral struct
{
lock mutex;
sizeClass int32;
noempty mSpanList;
empty mSpanList;
int32 nfree;
……
};
type mSpanList struct {
first *mSpan
last *mSpan
};
sizeclass: 也有成員 sizeclass,用于將MSpan進(jìn)行切分。
lock: 因?yàn)闀?huì)有多個(gè) P 過(guò)來(lái)競(jìng)爭(zhēng)。
nonempty: mspan 的雙向鏈表,當(dāng)前 mcentral 中可用的 mSpan list。
empty: 已經(jīng)被使用的,可以認(rèn)為是一種對(duì)所有 mSpan 的 track。MCentral存在于MHeap內(nèi)。
給對(duì)象 object 分配內(nèi)存的主要流程:
1.object size > 32K,則使用 mheap 直接分配。
2.object size < 16 byte,使用 mcache 的小對(duì)象分配器 tiny 直接分配。 (其實(shí) tiny 就是一個(gè)指針,暫且這么說(shuō)吧。)
3.object size > 16 byte && size <=32K byte 時(shí),先使用 mcache 中對(duì)應(yīng)的 size class 分配。
4.如果 mcache 對(duì)應(yīng)的 size class 的 span 已經(jīng)沒(méi)有可用的塊,則向 mcentral 請(qǐng)求。
5.如果 mcentral 也沒(méi)有可用的塊,則向 mheap 申請(qǐng),并切分。
6.如果 mheap 也沒(méi)有合適的 span,則想操作系統(tǒng)申請(qǐng)。
tcmalloc內(nèi)存分配器介紹
tcmalloc(thread-caching mallo)是google推出的一種內(nèi)存分配器。
具體策略:全局緩存堆和進(jìn)程的私有緩存。
1.對(duì)于一些小容量的內(nèi)存申請(qǐng)?jiān)囉眠M(jìn)程的私有緩存,私有緩存不足的時(shí)候可以再?gòu)娜志彺嫔暾?qǐng)一部分作為私有緩存。
2.對(duì)于大容量的內(nèi)存申請(qǐng)則需要從全局緩存中進(jìn)行申請(qǐng)。而大小容量的邊界就是32k。緩存的組織方式是一個(gè)單鏈表數(shù)組,數(shù)組的每個(gè)元素是一個(gè)單鏈表,鏈表中的每個(gè)元素具有相同的大小。
golang語(yǔ)言中MHeap就是全局緩存堆,MCache作為線程私有緩存。
在文章開(kāi)頭說(shuō)過(guò),內(nèi)存池就是利用MHeap實(shí)現(xiàn),大小切分則是在申請(qǐng)內(nèi)存的時(shí)候就做了,同時(shí)MCache分配內(nèi)存時(shí),可以用MCentral去取對(duì)應(yīng)的sizeClass,多線程管理方面則是通過(guò)MCache去實(shí)現(xiàn)。
總結(jié):
1.MHeap是一個(gè)全局變量,負(fù)責(zé)向系統(tǒng)申請(qǐng)內(nèi)存,mallocinit()函數(shù)進(jìn)行初始化。如果分配內(nèi)存對(duì)象大于32K直接向MHeap申請(qǐng)。
2.MCache線程級(jí)別管理內(nèi)存池,關(guān)聯(lián)結(jié)構(gòu)體P,主要是負(fù)責(zé)線程內(nèi)部?jī)?nèi)存申請(qǐng)。
3.MCentral連接MHeap與MCache的,MCache內(nèi)存不夠則向MCentral申請(qǐng),MCentral不夠時(shí)向MHeap申請(qǐng)內(nèi)存。
- 1 回答
- 0 關(guān)注
- 2293 瀏覽
添加回答
舉報(bào)