介紹
了解操作系統(tǒng)對(duì)內(nèi)存的管理機(jī)制后定页,現(xiàn)在可以去看下 Go 語言是如何利用底層的這些特性來優(yōu)化內(nèi)存的捂襟。Go 的內(nèi)存管理基本上參考 tcmalloc
來實(shí)現(xiàn)的专肪,只是細(xì)節(jié)上根據(jù)自身的需要做了一些小的優(yōu)化調(diào)整。
Go 的內(nèi)存是自動(dòng)管理的苞氮,我們可以隨意定義變量直接使用谈为,不需要考慮變量背后的內(nèi)存申請(qǐng)和釋放的問題旅挤。本文意在搞清楚 Go 在方面幫我們做了什么,使我們不用關(guān)心那些復(fù)雜內(nèi)存的問題伞鲫,還依舊能寫出較為高效的程序粘茄。
本篇只介紹 Go 的內(nèi)存管理模型,與其相關(guān)的還有逃逸分析和垃圾回收內(nèi)容,因?yàn)槠年P(guān)系柒瓣,打算后面找時(shí)間各自整理出一篇儒搭。
池
程序動(dòng)態(tài)申請(qǐng)內(nèi)存空間,是要使用系統(tǒng)調(diào)用的芙贫,比如 Linux 系統(tǒng)上是調(diào)用 mmap
方法實(shí)現(xiàn)的搂鲫。但對(duì)于大型系統(tǒng)服務(wù)來說,直接調(diào)用 mmap
申請(qǐng)內(nèi)存屹培,會(huì)有一定的代價(jià)默穴。比如:
- 系統(tǒng)調(diào)用會(huì)導(dǎo)致程序進(jìn)入內(nèi)核態(tài)怔檩,內(nèi)核分配完內(nèi)存后(也就是上篇所講的褪秀,對(duì)虛擬地址和物理地址進(jìn)行映射等操作),再返回到用戶態(tài)薛训。
- 頻繁申請(qǐng)很小的內(nèi)存空間媒吗,容易出現(xiàn)大量內(nèi)存碎片,增大操作系統(tǒng)整理碎片的壓力乙埃。
- 為了保證內(nèi)存訪問具有良好的局部性闸英,開發(fā)者需要投入大量的精力去做優(yōu)化,這是一個(gè)很重的負(fù)擔(dān)介袜。
如何解決上面的問題呢甫何?有經(jīng)驗(yàn)的人,可能很快就想到解決方案遇伞,那就是我們常說的對(duì)象池(也可以說是緩存)辙喂。
假設(shè)系統(tǒng)需要頻繁動(dòng)態(tài)申請(qǐng)內(nèi)存來存放一個(gè)數(shù)據(jù)結(jié)構(gòu),比如 [10]int
鸠珠。那么我們完全可以在程序啟動(dòng)之初巍耗,一次性申請(qǐng)幾百甚至上千個(gè) [10]int
。這樣完美的解決了上面遇到的問題:
- 不需要頻繁申請(qǐng)內(nèi)存了渐排,而是從對(duì)象池里拿炬太,程序不會(huì)頻繁進(jìn)入內(nèi)核態(tài)
- 因?yàn)橐淮涡陨暾?qǐng)一個(gè)連續(xù)的大空間,對(duì)象池會(huì)被重復(fù)利用驯耻,不會(huì)出現(xiàn)碎片亲族。
- 程序頻繁訪問的就是對(duì)象池背后的同一塊內(nèi)存空間,局部性良好可缚。
這樣做會(huì)造成一定的內(nèi)存浪費(fèi)霎迫,我們可以定時(shí)檢測(cè)對(duì)象池的大小,保證可用對(duì)象的數(shù)量在一個(gè)合理的范圍城看,少了就提前申請(qǐng)女气,多了就自動(dòng)釋放。
如果某種資源的申請(qǐng)和回收是昂貴的测柠,我們都可以通過建立資源池的方式來解決炼鞠,其他比如連接池缘滥,內(nèi)存池等等,都是一個(gè)思路谒主。
Golang 內(nèi)存管理
Golang 的內(nèi)存管理本質(zhì)上就是一個(gè)內(nèi)存池朝扼,只不過內(nèi)部做了很多的優(yōu)化。比如自動(dòng)伸縮內(nèi)存池大小霎肯,合理的切割內(nèi)存塊等等擎颖。
內(nèi)存池 mheap
Golang 的程序在啟動(dòng)之初,會(huì)一次性從操作系統(tǒng)那里申請(qǐng)一大塊內(nèi)存作為內(nèi)存池观游。這塊內(nèi)存空間會(huì)放在一個(gè)叫 mheap
的 struct
中管理搂捧,mheap 負(fù)責(zé)將這一整塊內(nèi)存切割成不同的區(qū)域,并將其中一部分的內(nèi)存切割成合適的大小懂缕,分配給用戶使用允跑。
我們需要先知道幾個(gè)重要的概念:
-
page
: 內(nèi)存頁,一塊8K
大小的內(nèi)存空間搪柑。Go 與操作系統(tǒng)之間的內(nèi)存申請(qǐng)和釋放聋丝,都是以page
為單位的。 -
span
: 內(nèi)存塊工碾,一個(gè)或多個(gè)連續(xù)的page
組成一個(gè)span
弱睦。如果把page
比喻成工人,span
可看成是小隊(duì)渊额,工人被分成若干個(gè)隊(duì)伍况木,不同的隊(duì)伍干不同的活。 -
sizeclass
: 空間規(guī)格端圈,每個(gè)span
都帶有一個(gè)sizeclass
焦读,標(biāo)記著該span
中的page
應(yīng)該如何使用。使用上面的比喻舱权,就是sizeclass
標(biāo)志著span
是一個(gè)什么樣的隊(duì)伍矗晃。 -
object
: 對(duì)象,用來存儲(chǔ)一個(gè)變量數(shù)據(jù)內(nèi)存空間宴倍,一個(gè)span
在初始化時(shí)张症,會(huì)被切割成一堆等大的object
。假設(shè)object
的大小是16B
鸵贬,span
大小是8K
俗他,那么就會(huì)把span
中的page
就會(huì)被初始化8K / 16B = 512
個(gè)object
。所謂內(nèi)存分配阔逼,就是分配一個(gè)object
出去兆衅。
示意圖:
上圖中,不同顏色代表不同的 span
,不同 span
的 sizeclass
不同羡亩,表示里面的 page
將會(huì)按照不同的規(guī)格切割成一個(gè)個(gè)等大的 object
用作分配摩疑。
使用 Go1.11.5 版本測(cè)試了下初始堆內(nèi)存應(yīng)該是 64M
左右,低版本會(huì)少點(diǎn)畏铆。
測(cè)試代碼:
package main
import "runtime"
var stat runtime.MemStats
func main() {
runtime.ReadMemStats(&stat)
println(stat.HeapSys)
}
內(nèi)部的整體內(nèi)存布局如下圖所示:
-
mheap.spans
:用來存儲(chǔ)page
和span
信息雷袋,比如一個(gè) span 的起始地址是多少,有幾個(gè) page辞居,已使用了多大等等楷怒。 -
mheap.bitmap
存儲(chǔ)著各個(gè)span
中對(duì)象的標(biāo)記信息,比如對(duì)象是否可回收等等瓦灶。 -
mheap.arena_start
: 將要分配給應(yīng)用程序使用的空間鸠删。
再說明下,圖中的空間大小倚搬,是 Go 向操作系統(tǒng)申請(qǐng)的虛擬內(nèi)存地址空間冶共,操作系統(tǒng)會(huì)將該段地址空間預(yù)留出來不做它用;而不是真的創(chuàng)建出這么大的虛擬內(nèi)存每界,在頁表中創(chuàng)建出這么大的映射關(guān)系。
mcentral
用途相同的 span
會(huì)以鏈表的形式組織在一起家卖。 這里的用途用 sizeclass
來表示眨层,就是指該 span
用來存儲(chǔ)哪種大小的對(duì)象。比如當(dāng)分配一塊大小為 n
的內(nèi)存時(shí)上荡,系統(tǒng)計(jì)算 n
應(yīng)該使用哪種 sizeclass
趴樱,然后根據(jù) sizeclass
的值去找到一個(gè)可用的 span
來用作分配。其中 sizeclass
一共有 67 種(Go1.5 版本酪捡,后續(xù)版本可能會(huì)不會(huì)改變不好說)叁征,如圖所示:
找到合適的 span
后,會(huì)從中取一個(gè) object
返回給上層使用逛薇。這些 span
被放在一個(gè)叫做 mcentral 的結(jié)構(gòu)中管理捺疼。
mheap 將從 OS 那里申請(qǐng)過來的內(nèi)存初始化成一個(gè)大 span
(sizeclass=0)。然后根據(jù)需要從這個(gè)大 span
中切出小 span
永罚,放在 mcentral 中來管理啤呼。大 span
由 mheap.freelarge
和 mheap.busylarge
等管理。如果 mcentral 中的 span
不夠用了呢袱,會(huì)從 mheap.freelarge
上再切一塊官扣,如果 mheap.freelarge
空間不夠,會(huì)再次從 OS 那里申請(qǐng)內(nèi)存重復(fù)上述步驟羞福。下面是 mheap 和 mcentral 的數(shù)據(jù)結(jié)構(gòu):
type mheap struct {
// other fields
lock mutex
free [_MaxMHeapList]mspan // free lists of given length惕蹄, 1M 以下
freelarge mspan // free lists length >= _MaxMHeapList, >= 1M
busy [_MaxMHeapList]mspan // busy lists of large objects of given length
busylarge mspan // busy lists of large objects length >= _MaxMHeapList
central [_NumSizeClasses]struct { // _NumSizeClasses = 67
mcentral mcentral
// other fields
}
// other fields
}
// Central list of free objects of a given size.
type mcentral struct {
lock mutex // 分配時(shí)需要加鎖
sizeclass int32 // 哪種 sizeclass
nonempty mspan // 還有可用的空間的 span 鏈表
empty mspan // 沒有可用的空間的 span 列表
}
這種方式可以避免出現(xiàn)外部碎片(文章最后面有外部碎片的介紹),因?yàn)橥粋€(gè) span 是按照固定大小分配和回收的,不會(huì)出現(xiàn)不可利用的一小塊內(nèi)存把內(nèi)存分割掉卖陵。這個(gè)設(shè)計(jì)方式與現(xiàn)代操作系統(tǒng)中的伙伴系統(tǒng)有點(diǎn)類似恋昼。
mcache
如果你閱讀的比較仔細(xì),會(huì)發(fā)現(xiàn)上面的 mcentral 結(jié)構(gòu)中有一個(gè) lock 字段赶促;因?yàn)椴l(fā)情況下液肌,很有可能多個(gè)線程同時(shí)從 mcentral 那里申請(qǐng)內(nèi)存的,必須要用鎖來避免沖突鸥滨。
但鎖是低效的嗦哆,在高并發(fā)的服務(wù)中,它會(huì)使內(nèi)存申請(qǐng)成為整個(gè)系統(tǒng)的瓶頸婿滓;所以在 mcentral 的前面又增加了一層 mcache老速。
每一個(gè) mcache 和每一個(gè)處理器(P) 是一一對(duì)應(yīng)的,也就是說每一個(gè) P 都有一個(gè) mcache 成員凸主。 Goroutine 申請(qǐng)內(nèi)存時(shí)橘券,首先從其所在的 P 的 mcache 中分配,如果 mcache 沒有可用 span
卿吐,再從 mcentral 中獲取旁舰,并填充到 mcache 中。
從 mcache 上分配內(nèi)存空間是不需要加鎖的嗡官,因?yàn)樵谕粫r(shí)間里箭窜,一個(gè) P 只有一個(gè)線程在其上面運(yùn)行,不可能出現(xiàn)競(jìng)爭衍腥。沒有了鎖的限制磺樱,大大加速了內(nèi)存分配。
所以整體的內(nèi)存分配模型大致如下圖所示:
其他優(yōu)化
zero size
有一些對(duì)象所需的內(nèi)存大小是0婆咸,比如 [0]int
, struct{}
竹捉,這種類型的數(shù)據(jù)根本就不需要內(nèi)存,所以沒必要走上面那么復(fù)雜的邏輯尚骄。
系統(tǒng)會(huì)直接返回一個(gè)固定的內(nèi)存地址块差。源碼如下:
func mallocgc(size uintptr, typ *_type, flags uint32) unsafe.Pointer {
// 申請(qǐng)的 0 大小空間的內(nèi)存
if size == 0 {
return unsafe.Pointer(&zerobase)
}
//.....
}
測(cè)試代碼:
package main
import (
"fmt"
)
func main() {
var (
a struct{}
b [0]int
c [100]struct{}
d = make([]struct{}, 1024)
)
fmt.Printf("%p\n", &a)
fmt.Printf("%p\n", &b)
fmt.Printf("%p\n", &c)
fmt.Printf("%p\n", &(d[0]))
fmt.Printf("%p\n", &(d[1]))
fmt.Printf("%p\n", &(d[1000]))
}
// 運(yùn)行結(jié)果,6 個(gè)變量的內(nèi)存地址是相同的:
0x1180f88
0x1180f88
0x1180f88
0x1180f88
0x1180f88
0x1180f88
Tiny對(duì)象
上面提到的 sizeclass=1
的 span乖仇,用來給 <= 8B
的對(duì)象使用憾儒,所以像 int32
, byte
, bool
以及小字符串等常用的微小對(duì)象,都會(huì)使用 sizeclass=1
的 span乃沙,但分配給他們 8B
的空間起趾,大部分是用不上的。并且這些類型使用頻率非常高警儒,就會(huì)導(dǎo)致出現(xiàn)大量的內(nèi)部碎片训裆。
所以 Go 盡量不使用 sizeclass=1
的 span眶根, 而是將 < 16B
的對(duì)象為統(tǒng)一視為 tiny 對(duì)象(tinysize)。分配時(shí)边琉,從 sizeclass=2
的 span 中獲取一個(gè) 16B
的 object 用以分配属百。如果存儲(chǔ)的對(duì)象小于 16B
,這個(gè)空間會(huì)被暫時(shí)保存起來 (mcache.tiny
字段)变姨,下次分配時(shí)會(huì)復(fù)用這個(gè)空間族扰,直到這個(gè) object 用完為止。
如圖所示:
以上圖為例定欧,這樣的方式空間利用率是 (1+2+8) / 16 * 100% = 68.75%
渔呵,而如果按照原始的管理方式,利用率是 (1+2+8) / (8 * 3) = 45.83%
砍鸠。
源碼中注釋描述扩氢,說是對(duì) tiny 對(duì)象的特殊處理牺陶,平均會(huì)節(jié)省 20%
左右的內(nèi)存场刑。
如果要存儲(chǔ)的數(shù)據(jù)里有指針,即使 <= 8B
也不會(huì)作為 tiny 對(duì)象對(duì)待盖文,而是正常使用 sizeclass=1
的 span
饭弓。
大對(duì)象
如上面所述双饥,最大的 sizeclass 最大只能存放 32K
的對(duì)象。如果一次性申請(qǐng)超過 32K
的內(nèi)存示启,系統(tǒng)會(huì)直接繞過 mcache 和 mcentral兢哭,直接從 mheap 上獲取,mheap 中有一個(gè) freelarge
字段管理著超大 span夫嗓。
總結(jié)
內(nèi)存的釋放過程,沒什么特別之處冲秽。就是分配的返過程舍咖,當(dāng) mcache 中存在較多空閑 span 時(shí),會(huì)歸還給 mcentral锉桑;而 mcentral 中存在較多空閑 span 時(shí)排霉,會(huì)歸還給 mheap;mheap 再歸還給操作系統(tǒng)民轴。這里就不詳細(xì)介紹了攻柠。
總結(jié)一下,這種設(shè)計(jì)之所以快后裸,主要有以下幾個(gè)優(yōu)勢(shì):
- 內(nèi)存分配大多時(shí)候都是在用戶態(tài)完成的瑰钮,不需要頻繁進(jìn)入內(nèi)核態(tài)。
- 每個(gè) P 都有獨(dú)立的 span cache微驶,多個(gè) CPU 不會(huì)并發(fā)讀寫同一塊內(nèi)存浪谴,進(jìn)而減少 CPU L1 cache 的 cacheline 出現(xiàn) dirty 情況开睡,增大 cpu cache 命中率。
- 內(nèi)存碎片的問題苟耻,Go 是自己在用戶態(tài)管理的篇恒,在 OS 層面看是沒有碎片的,使得操作系統(tǒng)層面對(duì)碎片的管理壓力也會(huì)降低凶杖。
- mcache 的存在使得內(nèi)存分配不需要加鎖胁艰。
當(dāng)然這不是沒有代價(jià)的,Go 需要預(yù)申請(qǐng)大塊內(nèi)存智蝠,這必然會(huì)出現(xiàn)一定的浪費(fèi)腾么,不過好在現(xiàn)在內(nèi)存比較廉價(jià),不用太在意寻咒。
總體上來看哮翘,Go 內(nèi)存管理也是一個(gè)金字塔結(jié)構(gòu):
這種設(shè)計(jì)比較通用,比如現(xiàn)在常見的 web 服務(wù)設(shè)計(jì)毛秘,為提升系統(tǒng)性能饭寺,一般都會(huì)設(shè)計(jì)成 客戶端 cache -> 服務(wù)端 cache -> 服務(wù)端 db
這幾層(當(dāng)然也可能會(huì)加入更多層),也是金字塔結(jié)構(gòu)叫挟。
將有限的計(jì)算資源布局成金字塔結(jié)構(gòu)艰匙,再將數(shù)據(jù)從熱到冷分為幾個(gè)層級(jí),放置在金字塔結(jié)構(gòu)上抹恳。調(diào)度器不斷做調(diào)整员凝,將熱數(shù)據(jù)放在金字塔頂層,冷數(shù)據(jù)放在金字塔底層奋献。
這種設(shè)計(jì)利用了計(jì)算的局部性特征健霹,認(rèn)為冷熱數(shù)據(jù)的交替是緩慢的。所以最怕的就是瓶蚂,數(shù)據(jù)訪問出現(xiàn)冷熱驟變糖埋。在操作系統(tǒng)上我們稱這種現(xiàn)象為內(nèi)存顛簸,系統(tǒng)架構(gòu)上通常被說成是緩存穿透窃这。其實(shí)都是一個(gè)意思瞳别,就是過度的使用了金字塔低端的資源。
這套內(nèi)部機(jī)制杭攻,使得開發(fā)高性能服務(wù)容易很多祟敛,通俗來講就是坑少了。一般情況下你隨便寫寫性能都不會(huì)太差兆解。我遇到過的導(dǎo)致內(nèi)存分配出現(xiàn)壓力的主要有 2 中情況:
- 頻繁申請(qǐng)大對(duì)象馆铁,常見于文本處理,比如寫一個(gè)海量日志分析的服務(wù)痪宰,很多日志內(nèi)容都很長叼架。這種情況建議自己維護(hù)一個(gè)對(duì)象([]byte)池畔裕,避免每次都要去 mheap 上分配。
- 濫用指針乖订,指針的存在不僅容易造成內(nèi)存浪費(fèi)扮饶,對(duì) GC 也會(huì)造成額外的壓力,所以盡量不要使用指針乍构。
附
內(nèi)存碎片
內(nèi)存碎片是系統(tǒng)在內(nèi)存管理過程中甜无,會(huì)不可避免的出現(xiàn)一塊塊無法被使用的內(nèi)存空間,這是內(nèi)存管理的產(chǎn)物哥遮。
內(nèi)部碎片
一般都是因?yàn)樽止?jié)對(duì)齊岂丘,如上面介紹 Tiny 對(duì)象分配的部分;為了字節(jié)對(duì)齊眠饮,會(huì)導(dǎo)致一部分內(nèi)存空間直接被放棄掉奥帘,不做分配使用。
再比如申請(qǐng) 28B 大小的內(nèi)存空間仪召,系統(tǒng)會(huì)分配 32B 的空間給它寨蹋,這也導(dǎo)致了其中 4B 空間是被浪費(fèi)掉的。這就是內(nèi)部碎片扔茅。
外部碎片
一般是因?yàn)閮?nèi)存的不斷分配釋放已旧,導(dǎo)致一些釋放的小內(nèi)存塊分散在內(nèi)存各處,無法被用以分配召娜。如圖:
上面的 8B 和 16B 的小空間运褪,很難再被利用起來。不過 Go 的內(nèi)存管理機(jī)制不會(huì)引起大量外部碎片玖瘸。
源代碼調(diào)用流程圖
針對(duì) Go1.5 源碼
runtime.MemStats 部分注釋
type MemStats struct {
// heap 分配出去的字節(jié)總數(shù)秸讹,和 HeapAlloc 值相同
Alloc uint64
// TotalAlloc 是 heap 累計(jì)分配出去字節(jié)數(shù),每次分配
// 都會(huì)累加這個(gè)值雅倒,但是釋放時(shí)候不會(huì)減少
TotalAlloc uint64
// Sys 是指程序從 OS 那里一共申請(qǐng)了多少內(nèi)存
// 因?yàn)槌?heap嗦枢,程序棧及其他內(nèi)部結(jié)構(gòu)都使用著從 OS 申請(qǐng)過來的內(nèi)存
Sys uint64
// Mallocs heap 累積分配出去的對(duì)象數(shù)
// 活動(dòng)中的對(duì)象總數(shù),即是 Mallocs - Frees
Mallocs uint64
// Frees 值 heap 累積釋放掉的對(duì)象總數(shù)
Frees uint64
// HeapAlloc 是分配出去的堆對(duì)象總和大小屯断,單位字節(jié)
// object 的聲明周期是 待分配 -> 分配使用 -> 待回收 -> 待分配
// 只要不是待分配的狀態(tài),都會(huì)加到 HeapAlloc 中
// 它和 HeapInuse 不同侣诺,HeapInuse 算的是使用中的 span殖演,
// 使用中的 span 里面可能還有很多 object 閑置
HeapAlloc uint64
// HeapSys 是 heap 從 OS 那里申請(qǐng)來的堆內(nèi)存大小,單位字節(jié)
// 指的是虛擬內(nèi)存的大小年鸳,不是物理內(nèi)存趴久,物理內(nèi)存大小 Go 語言層面是看不到的。
// 等于 HeapIdle + HeapInuse
HeapSys uint64
// HeapIdle 表示所有 span 中還有多少內(nèi)存是沒使用的
// 這些 span 上面沒有 object搔确,也就是完全閑置的彼棍,可以隨時(shí)歸還給 OS
// 也可以用于堆棧分配
HeapIdle uint64
// HeapInuse 是處在使用中的所有 span 中的總字節(jié)數(shù)
// 只要一個(gè) span 中有至少一個(gè)對(duì)象灭忠,那么就表示它被使用了
// HeapInuse - HeapAlloc 就表示已經(jīng)被切割成固定 sizeclass 的 span 里
HeapInuse uint64
// HeapReleased 是返回給操作系統(tǒng)的物理內(nèi)存總數(shù)
HeapReleased uint64
// HeapObjects 是分配出去的對(duì)象總數(shù)
// 如同 HeapAlloc 一樣,分配時(shí)增加座硕,被清理或被釋放時(shí)減少
HeapObjects uint64
// NextGC is the target heap size of the next GC cycle.
// NextGC 表示當(dāng) HeapAlloc 增長到這個(gè)值時(shí)弛作,會(huì)執(zhí)行一次 GC
// 垃圾回收的目標(biāo)是保持 HeapAlloc ≤ NextGC,每次 GC 結(jié)束
// 下次 GC 的目標(biāo)华匾,是根據(jù)當(dāng)前可達(dá)數(shù)據(jù)和 GOGC 參數(shù)計(jì)算得來的
NextGC uint64
// LastGC 是最近一次垃圾回收結(jié)束的時(shí)間 (the UNIX epoch).
LastGC uint64
// PauseTotalNs 是自程序啟動(dòng)起映琳, GC 造成 STW 暫停的累積納秒值
// STW 期間,所有的 goroutine 都會(huì)被暫停蜘拉,只有 GC 的 goroutine 可以運(yùn)行
PauseTotalNs uint64
// PauseNs 是循環(huán)隊(duì)列萨西,記錄著 GC 引起的 STW 總時(shí)間
//
// 一次 GC 循環(huán),可能會(huì)出現(xiàn)多次暫停旭旭,這里每項(xiàng)記錄的是一次 GC 循環(huán)里多次暫停的綜合谎脯。
// 最近一次 GC 的數(shù)據(jù)所在的位置是 PauseNs[NumGC%256]
PauseNs [256]uint64
// PauseEnd 是一個(gè)循環(huán)隊(duì)列,記錄著最近 256 次 GC 結(jié)束的時(shí)間戳持寄,單位是納秒源梭。
//
// 它和 PauseNs 的存儲(chǔ)方式一樣。一次 GC 可能會(huì)引發(fā)多次暫停际看,這里只記錄一次 GC 最后一次暫停的時(shí)間
PauseEnd [256]uint64
// NumGC 指完成 GC 的次數(shù)
NumGC uint32
// NumForcedGC 是指應(yīng)用調(diào)用了 runtime.GC() 進(jìn)行強(qiáng)制 GC 的次數(shù)
NumForcedGC uint32
// BySize 統(tǒng)計(jì)各個(gè) sizeclass 分配和釋放的對(duì)象的個(gè)數(shù)
//
// BySize[N] 統(tǒng)計(jì)的是對(duì)象大小 S咸产,滿足 BySize[N-1].Size < S ≤ BySize[N].Size 的對(duì)象
// 這里不記錄大于 BySize[60].Size 的對(duì)象分配
BySize [61]struct {
// Size 表示該 sizeclass 的每個(gè)對(duì)象的空間大小
// size class.
Size uint32
// Mallocs 是該 sizeclass 分配出去的對(duì)象的累積總數(shù)
// Size * Mallocs 就是累積分配出去的字節(jié)總數(shù)
// Mallocs - Frees 就是當(dāng)前正在使用中的對(duì)象總數(shù)
Mallocs uint64
// Frees 是該 sizeclass 累積釋放對(duì)象總數(shù)
Frees uint64
}
}