ntfs分配单元大小_万字长文图解 Go 内存管理分析:工具、分配和回收原理
“?golang的內(nèi)存分析工具怎么用?內(nèi)存和回收原理,這一篇就夠了”
大綱
1. 目錄
2. 由一個(gè)問(wèn)題展開(kāi)
3. 名字說(shuō)明
4. 內(nèi)存怎么采樣?
4.1 編譯期間逃逸分析
4.2 采樣的簡(jiǎn)單實(shí)現(xiàn)
4.3 內(nèi)存采樣的時(shí)機(jī)
4.4 內(nèi)存采樣的入口
4.5 內(nèi)存采樣的信息
4.6 golang的類型反射
5. 內(nèi)存分配
5.1 C語(yǔ)言你分配和釋放內(nèi)存怎么做?
5.2 內(nèi)存分配設(shè)計(jì)考慮的幾個(gè)問(wèn)題
5.3 golang的內(nèi)存分配
6. 內(nèi)存回收
6.1 golang協(xié)程搶占執(zhí)行
6.2 STW是怎么回事?
6.3 垃圾回收要求
6.4 golang版本迭代歷史
6.5 GC觸發(fā)條件
6.6 三色定義
6.7 GC流程
6.8 寫屏障
6.9 內(nèi)存可見(jiàn)性
6.10 注意問(wèn)題
1. 目錄
2. 由一個(gè)問(wèn)題展開(kāi)
golang從語(yǔ)言級(jí)別,就提供了完整的采樣和分析的機(jī)制。大家經(jīng)常使用 pprof 分析內(nèi)存占用。
但是不清楚怎么實(shí)現(xiàn)?不清楚怎么看指標(biāo)?不清楚 flat,cum的區(qū)別?我們就從這個(gè)問(wèn)題展開(kāi)。
3. 名字說(shuō)明
內(nèi)存分析的時(shí)候,有四個(gè)輸入選項(xiàng):
兩個(gè)輸出選項(xiàng):
思考幾個(gè)問(wèn)題:
4. 內(nèi)存怎么采樣?
4.1 編譯期間逃逸分析
說(shuō)明下,golang pprof是分析從堆上分配的內(nèi)存。golang的內(nèi)存在堆上,還是在棧上?這個(gè)不是我們決定的,就算你調(diào)用new這個(gè)關(guān)鍵字,也不一定是在堆上分配。
逃逸分析是golang的一個(gè)非常重要的一個(gè)點(diǎn)。對(duì)于內(nèi)存分配,垃圾回收的設(shè)計(jì)都有非常重要的影響。
4.2 采樣的簡(jiǎn)單實(shí)現(xiàn)
采樣的實(shí)現(xiàn)非常簡(jiǎn)單。簡(jiǎn)單描述流程:
累計(jì)分配:就是alloc 當(dāng)前在用 inuse:就是 alloc-free
4.3 內(nèi)存采樣的時(shí)機(jī)
采樣的時(shí)機(jī)說(shuō)3個(gè)點(diǎn):
但是注意一點(diǎn):并不是每一次分配內(nèi)存都會(huì)被采樣。也就是說(shuō)這里其實(shí)是有個(gè)權(quán)衡的。現(xiàn)在是每滿512KB才會(huì)采樣一次。這里的考慮是性能和采樣效果的權(quán)衡。因?yàn)椴蓸邮且馁M(fèi)性能的,是要取堆棧的。
怎么理解?舉個(gè)例子
理想情況下(不考慮其他任何影響):
那么有人會(huì)想,這樣豈不是會(huì)漏掉了很多內(nèi)存?統(tǒng)計(jì)還能用來(lái)排查問(wèn)題嗎?
這個(gè)是性能和效果的一個(gè)考慮,一般來(lái)講,我們是用pprof分析內(nèi)存占用的時(shí)候,在整個(gè)golang程序跑起來(lái)后,時(shí)時(shí)刻刻都在分配釋放內(nèi)存,每累計(jì)分配512KB,打點(diǎn)一次。雖然會(huì)漏掉一些內(nèi)存分配釋放,但是對(duì)每個(gè)結(jié)構(gòu)都是公平的。如果有一個(gè)內(nèi)存泄露分配行為,那么累計(jì)下來(lái)一定會(huì)被抓住的,并且是非常容易被抓住。
4.4 內(nèi)存采樣的入口
內(nèi)存采樣的入口,這個(gè)非常簡(jiǎn)單理解。肯定是一個(gè)在分配內(nèi)存的函數(shù)位置,一個(gè)是釋放內(nèi)存的位置。這里要特意提下上下文環(huán)境。因?yàn)間olang是垃圾回收類型的語(yǔ)言,內(nèi)存分配是完全交由golang自己管理,自己不能管理內(nèi)存。
兩個(gè)入口函數(shù):
這兩個(gè)是配套使用的采樣打點(diǎn)函數(shù)。而且一定是配套的。簡(jiǎn)單說(shuō):
- mProf_Malloc 是由業(yè)務(wù)程序行為(賦值器)觸發(fā)的,分配內(nèi)存嘛。比如你new了一個(gè)對(duì)象,這個(gè)對(duì)象在堆上,那么會(huì)調(diào)用 mallocgc 分配內(nèi)存,如果到了采樣點(diǎn),那么會(huì)調(diào)用 mProf_Malloc 采樣。
- mProf_Free 是回收器在確定并且將要回收內(nèi)存的時(shí)候調(diào)用的。是垃圾回收過(guò)程的一環(huán)。并且還要注意一點(diǎn),只有打過(guò)點(diǎn)的(mProf_Malloc計(jì)數(shù)過(guò)的對(duì)象,會(huì)有一個(gè)特殊處理),才會(huì)配套使用mProf_Free。
- 不是說(shuō),任意給一個(gè)內(nèi)存地址給你。你都知道這個(gè)是業(yè)務(wù)類型。
4.5 內(nèi)存采樣的信息
這里問(wèn)你的是,golang采樣是采樣啥?類型信息?這里也說(shuō)過(guò)一點(diǎn),內(nèi)存這里和類型系統(tǒng)是沒(méi)啥關(guān)系的。這里采樣的是分配棧,也就是分配路徑。
4.5.1 flat,cum 分別是怎么來(lái)的?
看個(gè)例子:
大家可以先猜下,我們看alloc_space。這個(gè)內(nèi)存會(huì)是怎么累計(jì)到的。實(shí)際統(tǒng)計(jì)如下:
和大家猜的一樣嗎?這些是怎么看。
首先說(shuō)幾個(gè)結(jié)論:
- cum和flat不相同的時(shí)候,代表這個(gè)函數(shù)除了自己分配內(nèi)存,自己內(nèi)部調(diào)用的別的函數(shù)也在分配內(nèi)存。
重點(diǎn)提示:這個(gè)要理解這個(gè),首先要知道,內(nèi)存采樣的是什么,內(nèi)存采樣的是分配棧。
解釋說(shuō)明
(圖中140M我們當(dāng)150M看哈,這里采樣少了第一次,細(xì)節(jié)原因可以看代碼,這里提一下,不做闡述。):
圖示
記住一句話:采樣是記錄分配堆棧,而不是類型信息。
4.6 golang的類型反射
思考幾個(gè)問(wèn)題:
先說(shuō)結(jié)論:golang里面,內(nèi)存塊是沒(méi)有攜帶對(duì)象類型信息的,這個(gè)跟C是一樣的。但是golang又有反射,golang的反射一定要基于interface使用。這個(gè)要仔細(xì)理解下。
因?yàn)?#xff0c;golang里面interface的結(jié)構(gòu)變量,是會(huì)記錄type類型的。
反射定律一:反射一定是基于接口的。是從接口到反射類型。
反射定律二:反射一定是基于接口的。是從反射類型到接口。
還是那句話,golang的反射一定是依賴接口類型的,一定是經(jīng)過(guò)接口倒騰過(guò)的。
因?yàn)楫?dāng)前接口這個(gè)類型對(duì)應(yīng)了兩個(gè)內(nèi)部結(jié)構(gòu):struct iface,struct eface,這兩個(gè)結(jié)構(gòu)都是會(huì)存儲(chǔ)type類型。以后的一切都是基于這個(gè)類型的。
5. 內(nèi)存分配
5.1 C語(yǔ)言你分配和釋放內(nèi)存怎么做?
思考一個(gè)問(wèn)題,在C語(yǔ)言里,我們分配內(nèi)存:
分配內(nèi)存的時(shí)候,傳入大小,拿到一個(gè)指針。
ptr = malloc(1024);釋放內(nèi)存的時(shí)候,直接傳入ptr,沒(méi)有任何其他參數(shù):
free (ptr);釋放的時(shí)候,怎么確定釋放哪些位置?如果要你自己實(shí)現(xiàn),有很多簡(jiǎn)單的思路,說(shuō)一個(gè)最簡(jiǎn)單的:分配的時(shí)候,不止分配1024字節(jié),還分配了其他的信息,帶head了。
這種分配方式有什么問(wèn)題:
5.2 內(nèi)存分配設(shè)計(jì)考慮的幾個(gè)問(wèn)題
5.3 golang的內(nèi)存分配
golang大方向的考慮就是基于局部性和碎片率來(lái)考慮的。使用的是和tcmalloc一致的設(shè)計(jì)。
5.3.1 整體設(shè)計(jì)
首先,內(nèi)存塊是不帶類型信息的。像我們?cè)贑語(yǔ)言里面,有時(shí)候?qū)崿F(xiàn)的簡(jiǎn)單的內(nèi)存池,在不考慮一些開(kāi)銷的時(shí)候,會(huì)把業(yè)務(wù)類型放到meta信息里,為的是排查問(wèn)題方便。golang內(nèi)存管理作為一個(gè)通用模塊,不會(huì)這么搞。
5.3.1.1 地址空間設(shè)計(jì)
很多時(shí)候,你查golang的資料,會(huì)看到這張圖:
這張圖有幾個(gè)信息比較重要:
注意幾個(gè)點(diǎn):
5.3.2 抽象對(duì)象概念
物理偏向概念:
邏輯偏向概念:
管理結(jié)構(gòu)層次概念:
mcache:每個(gè)M上的,管理內(nèi)存用的。我們都知道GMP架構(gòu),每個(gè)M都有自己的內(nèi)存cache管理,這樣是為了局部性。只是一個(gè)cache管理。mcentral:mheap結(jié)構(gòu)所有,也只是一個(gè)cache管理,但是是為所有人服務(wù)的。mheap:是真正負(fù)責(zé)分配和釋放物理內(nèi)存的。
5.3.3 局部性的設(shè)計(jì)
這個(gè)思路很簡(jiǎn)單,就是設(shè)計(jì)成局部性的一個(gè)層次設(shè)計(jì)。
5.3.3.1 mcache
mcache由于只歸屬自己的M,span一旦在這個(gè)結(jié)構(gòu)管理下,其他人是不可見(jiàn),不會(huì)去操作的。只有這個(gè)m會(huì)操作。所以自然就不需要加鎖。
5.3.3.2 mcentral
mcentral是所有人可見(jiàn)的。所以操作自然要互斥,這個(gè)的作用也是一個(gè)cache的統(tǒng)一管理。
5.3.3.3 mheap
這個(gè)是負(fù)責(zé)真實(shí)內(nèi)存分配和釋放的的一個(gè)結(jié)構(gòu)。
5.3.4 針對(duì)碎片率的設(shè)計(jì)
golang的內(nèi)存設(shè)計(jì)目標(biāo):碎片率平均12.5%左右。
說(shuō)明:
怎么的出來(lái)的這些值?經(jīng)驗(yàn)值吧,可能。
6. 內(nèi)存回收
6.1 golang協(xié)程搶占執(zhí)行
首先,golang沒(méi)有真正的搶占。golang調(diào)度單位為協(xié)程,所謂搶占,也就是強(qiáng)行剝奪執(zhí)行權(quán)。但是有一點(diǎn),golang本質(zhì)上是非搶占的,不像操作系統(tǒng)那樣,有時(shí)鐘中斷和時(shí)間片的概念。golang雖然里面是有一個(gè)搶占的概念,但是注意了,這個(gè)搶占是建議性質(zhì)的搶占,也就是說(shuō),如果有協(xié)程不聽(tīng)話,那是沒(méi)有辦法的,實(shí)現(xiàn)搶占的效果是要對(duì)方協(xié)程自己配合的。
一句話:系統(tǒng)想讓某個(gè)goroutine自己放棄執(zhí)行權(quán),會(huì)給這個(gè)協(xié)程設(shè)置一個(gè)魔數(shù),協(xié)程在切調(diào)度,或者其他時(shí)機(jī)檢查到了的時(shí)候,會(huì)感知到這一個(gè)行為。
當(dāng)前的搶占實(shí)現(xiàn)是:
所以,在golang里面,只要有函數(shù)調(diào)用,就會(huì)有感知搶占的時(shí)機(jī)。stw就是基于這個(gè)實(shí)現(xiàn)的。
思考一個(gè)問(wèn)題:
如果有一個(gè)猥瑣的函數(shù):非常耗時(shí),一直在做cpu操作,并且完全沒(méi)有函數(shù)調(diào)用。這種情況下,golang是沒(méi)有一點(diǎn)辦法的。那么這種情況會(huì)影響到整個(gè)程序的能力。
所以,我們平時(shí)寫函數(shù),一定要短小精悍,功能拆分合理。
6.2 STW是怎么回事?
STW:stop the world,也就是說(shuō)暫停說(shuō)由協(xié)程的調(diào)度和執(zhí)行。stw是怎么實(shí)現(xiàn)?stw的基礎(chǔ)就是上面提到的搶占實(shí)現(xiàn)。stw調(diào)用的目的是為了讓整個(gè)程序(賦值器停止),那么就需要?jiǎng)儕Z每一個(gè)協(xié)程的執(zhí)行。
stw在垃圾回收的幾個(gè)關(guān)鍵操作里是需要的,比如開(kāi)啟垃圾回收,需要stw,做好準(zhǔn)備工作。如果stw的時(shí)候,出現(xiàn)了猥瑣的函數(shù),那么會(huì)導(dǎo)致整個(gè)系統(tǒng)的能力降低。因?yàn)榇蠹叶荚诘饶阋粋€(gè)人。
6.3 垃圾回收要求
6.4 golang版本迭代歷史
6.5 GC觸發(fā)條件
- gcTriggerHeap 當(dāng)分配的內(nèi)存達(dá)到一定值就觸發(fā)GC
- gcTriggerTime 當(dāng)一定時(shí)間沒(méi)有執(zhí)行過(guò)GC就觸發(fā)
- gcTriggerCycle 要求啟動(dòng)新一輪的GC,一啟動(dòng)則跳過(guò),手動(dòng)觸發(fā)GC的runtime.GC( )會(huì)使用這個(gè)條件
6.6 三色定義
6.6.1 強(qiáng)三色
黑色對(duì)象不允許指向白色對(duì)象。
6.6.2 弱三色
黑色對(duì)象可以指向白色對(duì)象,但是前提是,該白色對(duì)象一定是處于灰色保護(hù)鏈中。
6.7 GC流程
這里不詳細(xì)闡述了。貼一張go1.8之前的圖:
當(dāng)下GC大概分為四個(gè)階段:
6.8 寫屏障
如果標(biāo)記和回收不用和應(yīng)用程序并發(fā),在標(biāo)記和回收整個(gè)過(guò)程直接stw,那么就簡(jiǎn)單了。golang為了提供低時(shí)延,就必須讓賦值器和回收器并發(fā)起來(lái)。但是在并發(fā)的過(guò)程中,賦值器和回收器對(duì)于引用樹(shù)的理解就會(huì)出現(xiàn)不一致,這里就一定要配合寫屏障技術(shù)。
寫屏障技術(shù),是動(dòng)態(tài)捕捉寫操作,維持回收正確性的技術(shù)。寫屏障就是一段 hook 代碼,編譯期間生成,運(yùn)行期間跟進(jìn)情況會(huì)調(diào)用到 hook 的代碼段,也就是寫屏障的代碼;
下面系統(tǒng)整體的討論下寫屏障的技術(shù)。
6.8.1 插入寫屏障
(Dijkstra '78)
writePointer ( slot, ptr ): // 無(wú)腦保護(hù)插入的新值 shade ( ptr ) *slot = ptr這個(gè)是另外一個(gè)通用的屏障技術(shù)。這個(gè)維護(hù)的是強(qiáng)三色不變式來(lái)保證正確性,保證黑色對(duì)象一定不能指向白色對(duì)象。golang使用的是這個(gè)屏障,插入屏障。按照道理,是幾乎完全不需要stw的。但是golang有一個(gè)處理,由于棧上面使用屏障會(huì)導(dǎo)致處理非常復(fù)雜,并且開(kāi)銷會(huì)非常大。所以當(dāng)前golang只針對(duì)堆上的寫操作做了屏障。
那么就會(huì)帶來(lái)一個(gè)問(wèn)題:所以當(dāng)一輪掃描完了之后,在標(biāo)記結(jié)束的階段,還需要重新掃描一遍goroutine棧,并且棧引用到的所有對(duì)象也要掃描。因?yàn)間oroutine有可能直接指向了白色對(duì)象。在掃描goroutine棧過(guò)程中,需要stw。這個(gè)也是go1.8以前的一個(gè)非常大的延遲來(lái)源。
(開(kāi)始的時(shí)候,stw掃描棧,得到灰色對(duì)象)
圖表演示
堆上路徑賦值:
step1:堆上對(duì)象賦值的時(shí)候,插入寫屏障,保護(hù)強(qiáng)三色不變式
step2:刪除的時(shí)候,沒(méi)啥問(wèn)題
棧上對(duì)象賦值:
step3:棧上對(duì)象賦值的時(shí)候,沒(méi)有寫屏障。白色對(duì)象直接被黑色對(duì)象引用。
step4:刪除灰色保護(hù)路徑。
所以才需要在mark terminato階段,重新掃描棧。
6.8.2 刪除寫屏障
(Yuasa '90)
writePointer ( slot, ptr ): // 刪除之前,保護(hù)原先白色或者灰色指向的數(shù)據(jù)塊 if ( isGery ( slot ) || isWhite ( slot ) ) shade ( *slot ) *slot = ptr這個(gè)是通用的一種寫屏障技術(shù)。golang并沒(méi)有實(shí)現(xiàn),而是實(shí)現(xiàn)了插入寫屏障。原因就在于:這個(gè)在垃圾回收之前,必須做一個(gè)快照掃描,這個(gè)就會(huì)對(duì)用戶時(shí)延有比較嚴(yán)重的影響。下面詳述。
主要流程:
(開(kāi)始的時(shí)候,stw掃描棧,得到灰色對(duì)象)
圖表演示
初始掃描快照后:
step1: 賦值。這里賦值是允許的,雖然是破壞了強(qiáng)三色不變式。但是還是符合弱三色不變式。
step2:刪除。這里就攔截了,必須置灰色。保證弱三色不變式。
回收精度:
刪除寫屏障的精度比插入寫屏障的精度更低。刪除的即使是最后一個(gè)指針,也會(huì)保留到下一輪,屬于一個(gè)浮動(dòng)垃圾。這個(gè)比插入屏障精度還低。因?yàn)?#xff0c;對(duì)于插入屏障所保留的對(duì)象,回收器至少可以確定曾在其中執(zhí)行了某些回收相關(guān)的操作(獲取或?qū)懭雽?duì)象的引用),但刪除屏障所保留的對(duì)象卻不一定被賦值器操作過(guò)。
為什么需要打快照?
刪除寫屏障,又叫快照屏障增量技術(shù)(或者說(shuō),一定要配合這個(gè)來(lái)做)。
golang為啥沒(méi)有用這個(gè)?
6.8.3 混合寫屏障
混合屏障是結(jié)合插入屏障和刪除屏障。
偽代碼:
writePointer (slot, ptr) : // 保護(hù)原來(lái)的(被刪除的) shade ( *slot ) if current stack is grey: // 如果對(duì)象為灰色,則還需要保護(hù)新指向的對(duì)象 shade ( ptr ) *slot = ptr(開(kāi)始的時(shí)候,stw掃描棧,得到黑色對(duì)象)
golang實(shí)際情況:
偽代碼如上。但是這里提出來(lái)一點(diǎn),golang根本不是和偽代碼說(shuō)的這樣。沒(méi)有做條件判斷,所以現(xiàn)在的回收精度很低。這個(gè)算是一個(gè)TodoList。
注意:使用了混合屏障,還是針對(duì)堆上的,棧上對(duì)象寫入還是沒(méi)有barrier。golang之前只使用插入屏障,關(guān)鍵在于棧對(duì)象沒(méi)有,導(dǎo)致棧上黑對(duì)象可能指向白對(duì)象。所以要rescan。因?yàn)槿绻籸escan,而且又破壞了弱三色不變式(沒(méi)有處于灰色保護(hù)鏈中),那么就丟數(shù)據(jù)了。
混合屏障,就是結(jié)合刪除屏障,保護(hù)這一個(gè)前提,代價(jià)就是進(jìn)一步降低回收精度。
圖表示例:
混合屏障就是要解決:棧指向白色對(duì)象,stw重新掃描棧的問(wèn)題。
step1:賦值白對(duì)象到黑對(duì)象引用,這個(gè)不會(huì)阻止這個(gè),也不會(huì)有寫屏障。就是一個(gè)正常的賦值。
step2:刪除指針的時(shí)候,意圖破壞弱三色不變式的時(shí)候,寫屏障就會(huì)把這個(gè)對(duì)象置灰色。
問(wèn)題一:如果有個(gè)還會(huì)想?由于棧上沒(méi)有寫屏障,這個(gè)刪除的對(duì)象式根指向的呢?如果存在以下場(chǎng)景?
step1:堆上的白色對(duì)象引用賦值給黑色棧對(duì)象。
step2:如果刪除指針,豈不是連弱三色不變式也破壞了?
這個(gè)怎么辦呢?
答案是:其實(shí)根本就不可能出現(xiàn)這個(gè)場(chǎng)景的引用圖。第一個(gè)圖就不會(huì)出現(xiàn)。因?yàn)殡m然沒(méi)有stw,但是掃描某個(gè)g的時(shí)候,這個(gè)g是暫停的。相當(dāng)于這個(gè)g棧是一個(gè)快照狀態(tài)。
混合寫屏障的棧,要么全黑,要么全白(單個(gè)棧)
那么這個(gè)暫停g這個(gè)是怎么做到的?
問(wèn)題二:如果是多個(gè)棧呢,那么就不是原子的快照了。比如下圖?那么就可能導(dǎo)致這種情況。
如果說(shuō)A和前面的黑色對(duì)象不屬于同一個(gè)g棧。那么是否可能會(huì)導(dǎo)致這種場(chǎng)景出現(xiàn)?分析下:
答案是:這里的關(guān)鍵在于第三步。G1的棧對(duì)象接受賦值,這個(gè)并不是憑空來(lái)的。那么一定是G1自己找來(lái)的,可達(dá)的對(duì)象。這個(gè)是一個(gè)前提。所以,如果能接受這樣的賦值,那么這個(gè)白色對(duì)象一定是處于G1棧的灰色保護(hù)下,因?yàn)镚1一定是可訪問(wèn)這個(gè)對(duì)象的。否則,根本就不能完成這個(gè)賦值。
混合寫屏障的場(chǎng)景,白色對(duì)象處于灰色保護(hù)下,但是只由堆上的灰色對(duì)象保護(hù)。注意理解這點(diǎn);
屏障生成示例:
runtime.gcWriteBarrier :
這么看起來(lái),就不存在 判斷stack是否為灰色的條件?
6.8.4 其他屏障
writePointer(slot, ptr): shade(*slot) shade(ptr) *slot = ptr優(yōu)點(diǎn):
缺點(diǎn):
這種屏障會(huì)導(dǎo)致比較多的屏障,兩倍。所以針對(duì)這個(gè)考慮權(quán)衡,會(huì)加一個(gè)stack條件判斷,就是我們看到的混合屏障的樣子。
6.9 內(nèi)存可見(jiàn)性
提一下golang的內(nèi)存可見(jiàn)性。在c里面,如果是在多線程環(huán)境,并發(fā)操作一些變量,需要考慮一些可見(jiàn)性的問(wèn)題。比如賦值一個(gè)變量,這個(gè)線程還有可能在寄存器里沒(méi)有刷下去,或者編譯器幫你優(yōu)化到寄存器中,不去內(nèi)存讀。所以有一個(gè)volatile關(guān)鍵字,強(qiáng)制去內(nèi)存讀。
golang是否有這個(gè)內(nèi)存可見(jiàn)性的問(wèn)題?
一句話,golang里面,只要你保證順序性,那么內(nèi)存一致性就沒(méi)有問(wèn)題。具體可以搜索happen-before的機(jī)制。
6.10 注意問(wèn)題
6.10.1 千萬(wàn)不要嘗試?yán)@過(guò)golang的類型系統(tǒng)
千萬(wàn)不要嘗試?yán)@過(guò)golang的類型系統(tǒng)。golang官方在提到uintptr類型的時(shí)候,都說(shuō)不要產(chǎn)生uintptr的臨時(shí)變量,因?yàn)楹苡锌赡軙?huì)導(dǎo)致gc的錯(cuò)誤回收(這個(gè)做過(guò)一個(gè)簡(jiǎn)單的驗(yàn)證,發(fā)現(xiàn)新版本的uintptr類型是會(huì)作為指針標(biāo)記的)。
舉一個(gè)極端的例子,如果你new了一個(gè)對(duì)象,然后把這個(gè)對(duì)象的地址保存在8個(gè)不連續(xù)的byte類型里,那就等著coredump吧。
6.10.2 在golang里按照c的思路實(shí)現(xiàn)一個(gè)內(nèi)存池很容易踩到巨坑。
比如現(xiàn)在你分配一個(gè)大內(nèi)存出來(lái)(1G的[ ]byte類型空間)。這是一個(gè)大內(nèi)存塊。并且golang沒(méi)有任何標(biāo)識(shí)這個(gè)地方標(biāo)識(shí)指針。
// 分配一個(gè)大內(nèi)存數(shù)組(1GB),數(shù)組元素是byte。那么自然每個(gè)元素都是不含指針的。begin := make([]byte, 1024*1024*1024)那么掃描是不會(huì)掃描這個(gè)內(nèi)部的。
內(nèi)存池分配器接口:func (ac *Allocator) Alloc (size int) unsafe.Pointer
用來(lái)分配對(duì)象,使用可能會(huì)導(dǎo)致莫名其妙的內(nèi)存錯(cuò)誤。假設(shè)用來(lái)分配對(duì)象T:
type T struct { s *S}t := (*T) (ac.Alloc(sizeT))t.s = &S{}T對(duì)象是從一個(gè)大數(shù)組里劃出來(lái)的,垃圾回收其實(shí)并不知道T這個(gè)對(duì)象。不過(guò)只要1G內(nèi)存池本身不被回收,T對(duì)象還是安全的。但是T里面的S,是golang走類型系統(tǒng)分配出來(lái)的,就會(huì)有問(wèn)題。
假設(shè)發(fā)生垃圾回收了,GC會(huì)認(rèn)為這個(gè)內(nèi)存空間是一個(gè)Byte數(shù)組,而不會(huì)掃描,那么t.s指向的對(duì)象認(rèn)為未被任何對(duì)象引用到,它會(huì)被清理掉。最后t.s就成了一個(gè)懸掛指針。
golang里面實(shí)現(xiàn)內(nèi)存分配器,適用處理兩種情況:
其實(shí),沒(méi)必要自己搞通用內(nèi)存池。一旦繞過(guò)了golang的類型系統(tǒng),就會(huì)出現(xiàn)坑。
推薦閱讀
Go GC 怎么標(biāo)記內(nèi)存?顏色是什么含義?圖解三色標(biāo)記法
站長(zhǎng) polarisxu
自己的原創(chuàng)文章
不限于 Go 技術(shù)
職場(chǎng)和創(chuàng)業(yè)經(jīng)驗(yàn)
Go語(yǔ)言中文網(wǎng)
每天為你
分享 Go 知識(shí)
Go愛(ài)好者值得關(guān)注
總結(jié)
以上是生活随笔為你收集整理的ntfs分配单元大小_万字长文图解 Go 内存管理分析:工具、分配和回收原理的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: frame中src怎么设置成一个变量_在
- 下一篇: startsBBS在nginx环境下的部