內(nèi)存管理
1布局
1.1操作系統(tǒng)內(nèi)存布局
1.1.1邏輯布局
1.1.2物理布局
1.2 GO 內(nèi)存布局
go沒有使用操作系統(tǒng)提供的內(nèi)存管理方案,而是自己實(shí)現(xiàn)了一套管理機(jī)制虚汛,其整體布局如下:
1.2.1 Arena
arena
區(qū)域就是我們所謂的堆區(qū)卵迂,Go動(dòng)態(tài)分配的內(nèi)存都是在這個(gè)區(qū)域底扳,它把內(nèi)存分割成8KB大小的頁(yè)寂嘉,一些頁(yè)組合起來稱為mspan
顾复。
1.2.2 Bitmap
bitmap
區(qū)域標(biāo)識(shí)arena
區(qū)域哪些地址保存了對(duì)象班挖,并且用4bit標(biāo)志位表示對(duì)象是否包含指針、GC標(biāo)記信息芯砸。bitmap
中一個(gè)byte大小的內(nèi)存對(duì)應(yīng)arena
區(qū)域中4個(gè)指針大邢糗健(指針大小為 8B )的內(nèi)存,所以bitmap
區(qū)域的大小是512GB/(4*8B)=16GB假丧。
1.2.3 Spans
Spans存放指向mspan
的指針
Mspan是arena
管理和分配的基本單元双揪,指向Arena中一段連續(xù)的頁(yè)
Go里mspan
的Size Class共有67種,每種mspan分割的object大小是8*2n的倍數(shù)包帚,這些class不是指數(shù)式翻倍的渔期,因?yàn)榇髩K連續(xù)內(nèi)存空間很少見,會(huì)導(dǎo)致各class的mspan
數(shù)量不均衡渴邦,這個(gè)是寫死在代碼里的:
// path: /usr/local/go/src/runtime/sizeclasses.go
const _NumSizeClasses = 67
var class_to_allocnpages = [_NumSizeClasses]uint8{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 3, 2, 3, 1, 3, 2, 3, 4, 5, 6, 1, 7, 6, 5, 4, 3, 5, 7, 2, 9, 7, 5, 8, 3, 10, 7, 4}
var class_to_size = [_NumSizeClasses] uint16{ 0, 8, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240, 256, 288, 320, 352, 384, 416, 448, 480, 512, 576, 640, 704, 768, 896, 1024, 1152, 1280, 1408, 1536, 1792, 2048, 2304, 2688, 3072, 3200, 3456, 4096, 4864, 5376, 6144, 6528, 6784, 6912, 8192, 9472, 9728, 10240, 10880, 12288, 13568, 14336, 16384, 18432, 19072, 20480, 21760, 24576, 27264, 28672, 32768}
Mcache
每個(gè)工作線程P都會(huì)綁定一個(gè)mcache
疯趟,本地緩存可用的mspan
資源,這樣就可以直接給Goroutine分配谋梭,因?yàn)椴淮嬖诙鄠€(gè)Goroutine競(jìng)爭(zhēng)的情況信峻,所以不會(huì)消耗鎖資源。
mcache
在初始化的時(shí)候是沒有任何mspan
資源的瓮床,在使用過程中會(huì)動(dòng)態(tài)地從mcentral
申請(qǐng)盹舞,之后會(huì)緩存下來产镐。
Mcentral
為所有mcache
提供切分好的mspan資源。每個(gè)central
保存一種特定大小的全局mspan
列表矾策,包括已分配出去的和未分配出去的磷账。
每個(gè)mcentral對(duì)應(yīng)一種mspan
,而mspan
的種類導(dǎo)致它分割的object大小不同贾虽。當(dāng)工作線程的mcache
中沒有合適(也就是特定大小的)的mspan
時(shí)就會(huì)從mcentral
獲取逃糟。
noscan
是沒有引用外部對(duì)象,GC時(shí)無需掃描的mspan
鏈表
mcentral
維護(hù)兩個(gè)雙向鏈表蓬豁,nonempty
表示有空閑對(duì)象的鏈表绰咽,empty
表示沒有空閑對(duì)象或 span
已經(jīng)被 mcache
緩存的 span
鏈表。這兩個(gè)變量名和實(shí)際的用途是反的地粪。
//go:notinheap
type mcentral struct {
lock mutex
sizeclass int32
nonempty mSpanList // list of spans with a free object, ie a nonempty free list
empty mSpanList // list of spans with no free objects (or cached in an mcache)
}
這個(gè)問題直到1.16版本才被修復(fù):
// Central list of free objects of a given size.
//
//go:notinheap
type mcentral struct {
spanclass spanClass
// partial and full contain two mspan sets: one of swept in-use
// spans, and one of unswept in-use spans. These two trade
// roles on each GC cycle. The unswept set is drained either by
// allocation or by the background sweeper in every GC cycle,
// so only two roles are necessary.
//
// sweepgen is increased by 2 on each GC cycle, so the swept
// spans are in partial[sweepgen/2%2] and the unswept spans are in
// partial[1-sweepgen/2%2]. Sweeping pops spans from the
// unswept set and pushes spans that are still in-use on the
// swept set. Likewise, allocating an in-use span pushes it
// on the swept set.
//
// Some parts of the sweeper can sweep arbitrary spans, and hence
// can't remove them from the unswept set, but will add the span
// to the appropriate swept list. As a result, the parts of the
// sweeper and mcentral that do consume from the unswept list may
// encounter swept spans, and these should be ignored.
partial [2]spanSet // list of spans with a free object
full [2]spanSet // list of spans with no free objects
}
并且還更新了spans
的存儲(chǔ)數(shù)據(jù)結(jié)構(gòu)為spanSet
:
// Central list of free objects of a given size.
//
//go:notinheap
type mcentral struct {
spanclass spanClass
// partial and full contain two mspan sets: one of swept in-use
// spans, and one of unswept in-use spans. These two trade
// roles on each GC cycle. The unswept set is drained either by
// allocation or by the background sweeper in every GC cycle,
// so only two roles are necessary.
//
// sweepgen is increased by 2 on each GC cycle, so the swept
// spans are in partial[sweepgen/2%2] and the unswept spans are in
// partial[1-sweepgen/2%2]. Sweeping pops spans from the
// unswept set and pushes spans that are still in-use on the
// swept set. Likewise, allocating an in-use span pushes it
// on the swept set.
//
// Some parts of the sweeper can sweep arbitrary spans, and hence
// can't remove them from the unswept set, but will add the span
// to the appropriate swept list. As a result, the parts of the
// sweeper and mcentral that do consume from the unswept list may
// encounter swept spans, and these should be ignored.
partial [2]spanSet // list of spans with a free object
full [2]spanSet // list of spans with no free objects
}
Mheap
代表Go程序持有的所有堆空間取募,Go程序使用一個(gè)mheap的全局對(duì)象_mheap來管理堆內(nèi)存
當(dāng)mcentral沒有空閑的mspan
時(shí),會(huì)向mheap
申請(qǐng)蟆技。而mheap
沒有資源時(shí)玩敏,會(huì)向操作系統(tǒng)申請(qǐng)新內(nèi)存。mheap
主要用于大對(duì)象的內(nèi)存分配质礼,以及管理未切割的mspan
旺聚,用于給mcentral
切割成小對(duì)象。
同時(shí)我們也看到眶蕉,mheap
中含有所有規(guī)格的mcentral
砰粹,所以,當(dāng)一個(gè)mcache
從mcentral
申請(qǐng)mspan
時(shí)造挽,只需要在獨(dú)立的mcentral
中使用鎖碱璃,并不會(huì)影響申請(qǐng)其他規(guī)格的mspan
。
heap
中central
數(shù)組尺寸是134饭入,因?yàn)槊糠Nspanclass
又細(xì)分為scan
和noscan
嵌器,scan
表示該mspan
中的對(duì)象包含指針,需要進(jìn)行GC掃描等管理操作圣拄,noscan
表示對(duì)象中都是非引用類型嘴秸,不需要進(jìn)行掃描。
2堆管理
2.1 特點(diǎn)
- 每次從操作系統(tǒng)申請(qǐng)一大塊的內(nèi)存庇谆,由Go來對(duì)這塊內(nèi)存做分配,減少系統(tǒng)調(diào)用
- 內(nèi)存分配算法采用Google的
TCMalloc
算法凭疮。其核心思想就是把內(nèi)存切分的非常的細(xì)小饭耳,分為多級(jí)管理,以降低鎖的粒度 - 回收對(duì)象內(nèi)存時(shí)执解,并沒有將其真正釋放掉寞肖,只是放回預(yù)先分配的大塊內(nèi)存中纲酗,以便復(fù)用。只有內(nèi)存閑置過多的時(shí)候新蟆,才會(huì)嘗試歸還部分內(nèi)存給操作系統(tǒng)觅赊,降低整體開銷
- Go內(nèi)存管理追求低延遲,適合高并發(fā)IO密集場(chǎng)景琼稻,高吞吐量用Java吮螺,高性能用C
2.2 分配
- 32KB 的對(duì)象,直接從
mheap
上分配帕翻; - <=16B 且沒有指針的對(duì)象使用
mcache
的tiny分配器分配鸠补; - (16B,32KB] 的對(duì)象,首先計(jì)算對(duì)象的規(guī)格大小嘀掸,然后使用
mcache
中相應(yīng)規(guī)格大小的mspan分配紫岩; - 如果
mcache
沒有相應(yīng)規(guī)格大小的mspan
,則向mcentral
申請(qǐng)
○ 如果當(dāng)前的span
中并沒有可以使用的元素睬塌,這時(shí)就需要從mcentral
中加鎖查找泉蝌。之前介紹過,在mcentral中有兩種類型的span
鏈表揩晴,分別是有空閑元素的nonempty
勋陪,以及沒有空閑元素的empty
鏈表。會(huì)分別遍歷這兩個(gè)列表,查找是否有可用的span
文狱。這是由于有些span
可能已經(jīng)被垃圾回收器標(biāo)記為空閑了粥鞋,只是還沒有來得及清理。這些Span在清掃后仍然是可以使用的瞄崇,因此需要遍歷呻粹。
○ 如果在mcentral
元素中查找到有空閑元素的span
,則將其賦值到mcache
中苏研,并更新allocCache
等浊,同時(shí)還需要將span
添加到mcentral
的empty
鏈表中去。 - 如果
mcentral
沒有相應(yīng)規(guī)格大小的mspan
摹蘑,則向mheap
申請(qǐng)
○ 當(dāng)要分配的page
過大或者在邏輯處理器P的cache
中沒有找到可用的頁(yè)數(shù)時(shí)筹燕,就需要對(duì)mheap加鎖,并在整個(gè)mheap
管理的虛擬地址空間的位圖中查找是否有可用的pages
衅鹿。而且其在本質(zhì)上涉及到Go語(yǔ)言是如何對(duì)線性的地址空間進(jìn)行位圖管理的撒踪。
○ 管理線性的地址空間的位圖結(jié)構(gòu)叫做基數(shù)樹(radix tree), 他和一般的基數(shù)樹結(jié)構(gòu)有點(diǎn)不太一樣大渤,這個(gè)名字很大一部分是由于父節(jié)點(diǎn)包含了子節(jié)點(diǎn)的若干信息制妄。 - 如果
mheap
中也沒有合適大小的mspan
,則向操作系統(tǒng)申請(qǐng)
○ 每一次向操作系統(tǒng)申請(qǐng)內(nèi)存時(shí)泵三,Go語(yǔ)言規(guī)定必須為heapArena
大小的倍數(shù)耕捞。heapArena
是和平臺(tái)有關(guān)的內(nèi)存大小衔掸,在unix 64位系統(tǒng)中,其大小為64M俺抽。這意味著即便需要的內(nèi)存大小很少敞映,最終也至少向操作系統(tǒng)申請(qǐng)64M。多申請(qǐng)的內(nèi)存可以用于下次分配使用磷斧。
2.3 回收
2.3.1 STW(Go < 1.3)
go runtime在一定條件下(內(nèi)存超過閾值振愿、達(dá)到GC周期時(shí)間2min或者執(zhí)行runtime.GC)會(huì)觸發(fā)GC
首先啟動(dòng)垃圾回收goroutine,這個(gè)goroutine會(huì)將其他goroutine執(zhí)行到安全點(diǎn)后停止
停止后將P與M全部解綁
再把所有Goroutine放到全局調(diào)度隊(duì)列中
2.3.2 STW + 并發(fā)清除(Go < 1.5)
Mark完成后馬上就重新啟動(dòng)被暫停的任務(wù)了瞳抓,而是讓sweep任務(wù)和普通協(xié)程任務(wù)一樣并行的和其他任務(wù)一起執(zhí)行
2.3.3 三色標(biāo)記+插入寫屏障(Go < 1.8)
插入寫屏障重掃階段需要棧區(qū)進(jìn)行STW(Java CMS)埃疫,刪除寫屏障則需要在初始階段STW并生成快照SATB(Java G1),并且Go的棧不支持做寫屏障
2.3.4
Go 1.8采用一種混合的 write barrier 方式 (Yuasa-style deletion write barrier [Yuasa ‘90] 和 Dijkstra-style insertion write barrier [Dijkstra ‘78])來避免堆棧重新掃描孩哑,優(yōu)點(diǎn)如下:
- 三色標(biāo)記的變化是單向的:白->灰->黑
- 混合屏障不需要讀屏障
用一句話來說栓霜,寫屏障可以捕獲“”,詳見Golang三色標(biāo)記+混合寫屏障GC模式全分析
3 棧管理
3.1 特點(diǎn)
在Go應(yīng)用程序運(yùn)行時(shí)横蜒,每個(gè)goroutine都維護(hù)著一個(gè)自己的棧區(qū)胳蛮,這個(gè)棧區(qū)只能自己使用不能被其他goroutine使用。棧區(qū)的初始大小是2KB
(比x86_64架構(gòu)下線程的默認(rèn)棧2M要小很多)丛晌,在goroutine運(yùn)行的時(shí)候棧區(qū)會(huì)按照需要增長(zhǎng)和收縮仅炊,占用的內(nèi)存最大限制的默認(rèn)值在64位系統(tǒng)上是1GB。棧大小的初始值和上限這部分的設(shè)置都可以在Go的源碼runtime/stack.go
里找到澎蛛。
type g struct {
stack stack
...
}
type stack struct {
lo uintptr
hi uintptr
}
其實(shí)棧內(nèi)存空間抚垄、結(jié)構(gòu)和初始大小在最開始并不是2KB
,也是經(jīng)過了幾個(gè)版本的更迭:
- v1.0 ~ v1.1 — 最小棧內(nèi)存空間為
4KB
谋逻; - v1.2 — 將最小棧內(nèi)存提升到了
8KB
呆馁; - v1.3 — 使用連續(xù)棧替換之前版本的分段棧;
- v1.4 — 將最小棧內(nèi)存降低到了
2KB
毁兆;
函數(shù)棧中包含:
- 入?yún)⒊鰠⑿畔?/li>
- 調(diào)用函數(shù)信息
- 函數(shù)返回地址
- 局部變量浙滤、常量
- 寄存器信息
3.1.1 全局棧緩存
棧空間在運(yùn)行時(shí)中包含兩個(gè)重要的全局變量气堕,分別是 runtime.stackpool
和runtime.stackLarge
纺腊,這兩個(gè)變量分別表示全局的棧緩存和大棧緩存,前者可以分配小于 32KB
的內(nèi)存茎芭,后者用來分配大于 32KB
的椧灸ぃ空間:
// Number of orders that get caching. Order 0 is FixedStack
// and each successive order is twice as large.
// We want to cache 2KB, 4KB, 8KB, and 16KB stacks. Larger stacks
// will be allocated directly.
// Since FixedStack is different on different systems, we
// must vary NumStackOrders to keep the same maximum cached size.
// OS | FixedStack | NumStackOrders
// -----------------+------------+---------------
// linux/darwin/bsd | 2KB | 4
// windows/32 | 4KB | 3
// windows/64 | 8KB | 2
// plan9 | 4KB | 3
_NumStackOrders = 4 - sys.PtrSize/4*sys.GoosWindows - 1*sys.GoosPlan9
var stackpool [_NumStackOrders]mSpanList
type stackpoolItem struct {
mu mutex
span mSpanList
}
var stackLarge struct {
lock mutex
free [heapAddrBits - pageShift]mSpanList
}
//go:notinheap
type mSpanList struct {
first *mspan // first span in list, or nil if none
last *mspan // last span in list, or nil if none
}
可以看到這兩個(gè)用于分配空間的全局變量都與內(nèi)存管理單元 runtime.mspan
有關(guān),所以我們棧內(nèi)容的申請(qǐng)也和堆的申請(qǐng)相似梅桩,是先去當(dāng)前線程的對(duì)應(yīng)尺寸的mcache
里去申請(qǐng)次氨,不夠的時(shí)候mache會(huì)從全局的mcental
里取內(nèi)存等等。
其實(shí)從調(diào)度器和內(nèi)存分配的角度來看摘投,如果運(yùn)行時(shí)只使用全局變量來分配內(nèi)存的話煮寡,勢(shì)必會(huì)造成線程之間的鎖競(jìng)爭(zhēng)進(jìn)而影響程序的執(zhí)行效率,棧內(nèi)存由于與線程關(guān)系比較密切犀呼,所以在每一個(gè)線程緩存 runtime.mcache
中都加入了棧緩存減少鎖競(jìng)爭(zhēng)影響幸撕。
type mcache struct {
...
alloc [numSpanClasses]*mspan
stackcache [_NumStackOrders]stackfreelist
...
}
type stackfreelist struct {
list gclinkptr
size uintptr
}
3.2 分配
3.2.1 分段棧(Go < 1.3)
隨著goroutine 調(diào)用的函數(shù)層級(jí)的深入或者局部變量需要的越來越多時(shí),運(yùn)行時(shí)會(huì)調(diào)用 runtime.morestack
和 runtime.newstack
創(chuàng)建一個(gè)新的椡獗郏空間坐儿,這些棧空間是不連續(xù)的宋光,但是當(dāng)前 goroutine 的多個(gè)椕部螅空間會(huì)以雙向鏈表的形式串聯(lián)起來,運(yùn)行時(shí)會(huì)通過指針找到連續(xù)的棧片段.
分段棧雖然能夠按需為當(dāng)前 goroutine 分配內(nèi)存并且及時(shí)減少內(nèi)存的占用罪佳,但是它也存在一個(gè)比較大的問題:如果當(dāng)前 goroutine 的棧幾乎充滿逛漫,那么任意的函數(shù)調(diào)用都會(huì)觸發(fā)棧的擴(kuò)容,當(dāng)函數(shù)返回后又會(huì)觸發(fā)棧的收縮赘艳,如果在一個(gè)循環(huán)中調(diào)用函數(shù)酌毡,棧的分配和釋放就會(huì)造成巨大的額外開銷,這被稱為蕾管。
為了解決這個(gè)問題枷踏,Go在1.2版本的時(shí)候不得不將棧的初始化內(nèi)存從4KB增大到了8KB。后來把采用連續(xù)棧結(jié)構(gòu)后掰曾,又把初始棧大小減小到了2KB旭蠕。
3.2.2 連續(xù)棧(Go >= 1.3)
連續(xù)棧可以解決分段棧中存在的兩個(gè)問題旷坦,其核心原理就是每當(dāng)程序的椞桶荆空間不足時(shí),初始化一片比舊棧大兩倍的新棧并將原棧中的所有值都遷移到新的棧中塞蹭,新的局部變量或者函數(shù)調(diào)用就有了充足的內(nèi)存空間孽江。使用連續(xù)棧機(jī)制時(shí),椃纾空間不足導(dǎo)致的擴(kuò)容會(huì)經(jīng)歷以下幾個(gè)步驟:
- 調(diào)用
runtime.newstack
在內(nèi)存空間中分配更大的棧內(nèi)存空間岗屏,舊棧的大小是通過我們上面說的保存在goroutine中的stack信息里記錄的棧區(qū)內(nèi)存邊界計(jì)算出來的,然后用舊棧兩倍的大小創(chuàng)建新棧漱办,創(chuàng)建前會(huì)檢查是新棧的大小是否超過了單個(gè)棧的內(nèi)存上限这刷; - 如果目標(biāo)棧的大小沒有超出程序的限制,會(huì)將 goroutine 切換至
_Gcopystack
狀態(tài)并調(diào)用runtime.copystack
開始棧的拷貝娩井,在拷貝棧的內(nèi)存之前暇屋,運(yùn)行時(shí)會(huì)先通過runtime.stackalloc
函數(shù)分配新的棧空間洞辣; - 將指向舊棧對(duì)應(yīng)變量的指針重新指向新棧咐刨;
- 調(diào)用
runtime.stackfree
銷毀并回收舊棧的內(nèi)存空間昙衅。
copystack會(huì)把舊棧里的所有內(nèi)容拷貝到新棧里然后調(diào)整所有指向舊棧的變量的指針指向到新棧, 我們可以用下面這個(gè)程序驗(yàn)證下定鸟,棧擴(kuò)容后同一個(gè)變量的內(nèi)存地址會(huì)發(fā)生變化而涉。
package main
func main() {
var x [10]int
println(&x)
a(x)
println(&x)
}
//go:noinline
func a(x [10]int) {
println(`func a`)
var y [100]int
b(y)
}
//go:noinline
func b(x [100]int) {
println(`func b`)
var y [1000]int
c(y)
}
//go:noinline
func c(x [1000]int) {
println(`func c`)
}
程序的輸出可以看到在棧擴(kuò)容前后,變量x的內(nèi)存地址的變化:
0xc000030738
...
...
0xc000081f38
3.3 回收
在goroutine運(yùn)行的過程中联予,如果棧區(qū)的空間使用率不超過1/4啼县,那么在垃圾回收的時(shí)候使用runtime.shrinkstack進(jìn)行棧縮容沸久,當(dāng)然進(jìn)行縮容前會(huì)執(zhí)行一堆前置檢查季眷,都通過了才會(huì)進(jìn)行縮容:
func shrinkstack(gp *g) {
...
oldsize := gp.stack.hi - gp.stack.lo
newsize := oldsize / 2
if newsize < _FixedStack {
return
}
avail := gp.stack.hi - gp.stack.lo
if used := gp.stack.hi - gp.sched.sp + _StackLimit; used >= avail/4 {
return
}
copystack(gp, newsize)
}
如果要觸發(fā)棧的縮容,新棧的大小會(huì)是原始棧的一半卷胯,不過如果新棧的大小低于程序的最低限制 2KB子刮,那么縮容的過程就會(huì)停止∷薪撸縮容也會(huì)調(diào)用擴(kuò)容時(shí)使用的 runtime.copystack 函數(shù)開辟新的椈案妫空間,將舊棧的數(shù)據(jù)拷貝到新棧以及調(diào)整原來指針的指向卵慰。
在下面的例子里沙郭,當(dāng)main函數(shù)里的其他函數(shù)執(zhí)行完后,只有main函數(shù)還在棧區(qū)的空間里裳朋,如果這個(gè)時(shí)候系統(tǒng)進(jìn)行垃圾回收就會(huì)對(duì)這個(gè)goroutine的棧區(qū)進(jìn)行縮容病线。在這里我們可以在程序里通過調(diào)用runtime.GC,強(qiáng)制系統(tǒng)進(jìn)行垃圾回收鲤嫡,來試驗(yàn)看一下椝吞簦縮容的過程和效果:
func main() {
var x [10]int
println(&x)
a(x)
runtime.GC()
println(&x)
}
如果要觸發(fā)棧的縮容,新棧的大小會(huì)是原始棧的一半暖眼,不過如果新棧的大小低于程序的最低限制 2KB诫肠,那么縮容的過程就會(huì)停止栋豫。縮容也會(huì)調(diào)用擴(kuò)容時(shí)使用的 runtime.copystack 函數(shù)開辟新的椢Х剩空間怨愤,將舊棧的數(shù)據(jù)拷貝到新棧以及調(diào)整原來指針的指向篮愉。
在下面的例子里猪勇,當(dāng)main函數(shù)里的其他函數(shù)執(zhí)行完后,只有main函數(shù)還在棧區(qū)的空間里,如果這個(gè)時(shí)候系統(tǒng)進(jìn)行垃圾回收就會(huì)對(duì)這個(gè)goroutine的棧區(qū)進(jìn)行縮容掀泳。在這里我們可以在程序里通過調(diào)用runtime.GC
藕畔,強(qiáng)制系統(tǒng)進(jìn)行垃圾回收韭邓,來試驗(yàn)看一下棧縮容的過程和效果:
func main() {
var x [10]int
println(&x)
a(x)
runtime.GC()
println(&x)
}
修改源碼文件runtime.stack.go
,把常量stackDebug
的值修改為1袜茧,執(zhí)行命令go build -gcflags -S main.go
后會(huì)看到類似下面的輸出:
...
shrinking stack 32768->16384
stackalloc 16384
allocated 0xc000076000
copystack gp=0xc000000180 [0xc00007a000 0xc000081e60 0xc000082000] -> [0xc000076000 0xc000079e60 0xc00007a000]/16384
...
3.4 問題
問:局部變量什么時(shí)候分配在堆上俺夕,什么時(shí)候在棧上?
答:這個(gè)由編譯器決定,和具體的語(yǔ)法無關(guān)捌议,各版本golang的實(shí)現(xiàn)也有差異轿曙,golang刻意弱化了堆和棧的概念。
Golang 編譯器會(huì)將函數(shù)的局部變量分配到函數(shù)棧幀(stack frame)上。然而悦陋,如果編譯器不能確保變量在函數(shù) return 之后不再被引用(逃逸分析)幸逆,編譯器就會(huì)將變量分配到堆上。而且,如果一個(gè)局部變量非常大还绘,那么它也應(yīng)該被分配到堆上而不是棧上楚昭。
比如這段代碼,就不會(huì)在堆上分配內(nèi)存拍顷,即使我們用new分配:
const Width, Height = 640, 480
type Cursor struct {
X, Y int
}
func Center(c *Cursor) {
c.X += Width / 2
c.Y += Height / 2
}
func CenterCursor() {
c := new(Cursor)
Center(c)
fmt.Println(c.X, c.Y)
}
驗(yàn)證結(jié)果如下:
go tool compile -m test.go
test.go:17: can inline Center
test.go:24: inlining call to Center
test.go:25: c.X escapes to heap
test.go:25: c.Y escapes to heap
test.go:23: CenterCursor new(Cursor) does not escape
test.go:25: CenterCursor ... argument does not escape
test.go:17: Center c does not escape
這段代碼則會(huì)在堆上分配對(duì)象:
package main
import (
"fmt"
)
func main() {
var a [1]int
c := a[:]
fmt.Println(c)
}
匯編代碼有調(diào)用newobject抚太,其中test.go:8說明變量a的內(nèi)存是在堆上分配的:
go tool compile -S test.golang
"".main t=1 size=336 value=0 args=0x0 locals=0x98
0x0000 00000 (test.go:7) TEXT "".main(SB), $152-0
0x0000 00000 (test.go:7) MOVQ (TLS), CX
0x0009 00009 (test.go:7) LEAQ -24(SP), AX
0x000e 00014 (test.go:7) CMPQ AX, 16(CX)
0x0012 00018 (test.go:7) JLS 320
0x0018 00024 (test.go:7) SUBQ $152, SP
0x001f 00031 (test.go:7) FUNCDATA $0, gclocals·7d2d5fca80364273fb07d5820a76fef4(SB)
0x001f 00031 (test.go:7) FUNCDATA $1, gclocals·6e96661712a005168eba4ed6774db961(SB)
0x001f 00031 (test.go:8) LEAQ type.[1]int(SB), BX
0x0026 00038 (test.go:8) MOVQ BX, (SP)
0x002a 00042 (test.go:8) PCDATA $0, $0
0x002a 00042 (test.go:8) CALL runtime.newobject(SB)
0x002f 00047 (test.go:8) MOVQ 8(SP), AX
0x0034 00052 (test.go:9) CMPQ AX, $0
0x0038 00056 (test.go:9) JEQ $1, 313
0x003e 00062 (test.go:9) MOVQ $1, DX
0x0045 00069 (test.go:9) MOVQ $1, CX
3.5 閉包
- Go語(yǔ)言支持閉包
- 返回閉包時(shí)并不是單純返回一個(gè)函數(shù)尿贫,而是返回了一個(gè)結(jié)構(gòu)體,記錄下函數(shù)返回地址和引用的環(huán)境中的變量地址
4 Runtime與Debug
runtime.GC函數(shù)
會(huì)讓運(yùn)行時(shí)系統(tǒng)進(jìn)行一次強(qiáng)制性的垃圾收集:
- 強(qiáng)制的垃圾回收:不管怎樣爱沟,都要進(jìn)行的垃圾回收帅霜。
- 非強(qiáng)制的垃圾回收:只會(huì)在一定條件下進(jìn)行的垃圾回收(即運(yùn)行時(shí),系統(tǒng)自上次垃圾回收之后新申請(qǐng)的堆內(nèi)存的單元(也成為單元增量)達(dá)到指定的數(shù)值)呼伸。
debug.SetGCPercent函數(shù)
用于設(shè)置一個(gè)比率(垃圾收集比率),前面所說的單元增量與前一次垃圾收集時(shí)的碎內(nèi)存的單元數(shù)量和此垃圾手機(jī)比率有關(guān)钝尸。
<觸發(fā)垃圾收集的堆內(nèi)存單元增量>=<上一次垃圾收集完的堆內(nèi)存單元數(shù)量>*(<垃圾收集比率>/100)