Go 語言內(nèi)存管理(二):Go 內(nèi)存管理

介紹

了解操作系統(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à)默穴。比如:

  1. 系統(tǒng)調(diào)用會(huì)導(dǎo)致程序進(jìn)入內(nèi)核態(tài)怔檩,內(nèi)核分配完內(nèi)存后(也就是上篇所講的褪秀,對(duì)虛擬地址和物理地址進(jìn)行映射等操作),再返回到用戶態(tài)薛训。
  2. 頻繁申請(qǐng)很小的內(nèi)存空間媒吗,容易出現(xiàn)大量內(nèi)存碎片,增大操作系統(tǒng)整理碎片的壓力乙埃。
  3. 為了保證內(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。這樣完美的解決了上面遇到的問題:

  1. 不需要頻繁申請(qǐng)內(nèi)存了渐排,而是從對(duì)象池里拿炬太,程序不會(huì)頻繁進(jìn)入內(nèi)核態(tài)
  2. 因?yàn)橐淮涡陨暾?qǐng)一個(gè)連續(xù)的大空間,對(duì)象池會(huì)被重復(fù)利用驯耻,不會(huì)出現(xiàn)碎片亲族。
  3. 程序頻繁訪問的就是對(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è)叫 mheapstruct 中管理搂捧,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,不同 spansizeclass 不同羡亩,表示里面的 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ǔ) pagespan 信息雷袋,比如一個(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 中來管理啤呼。大 spanmheap.freelargemheap.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=1span饭弓。

大對(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ì):

  1. 內(nèi)存分配大多時(shí)候都是在用戶態(tài)完成的瑰钮,不需要頻繁進(jìn)入內(nèi)核態(tài)。
  2. 每個(gè) P 都有獨(dú)立的 span cache微驶,多個(gè) CPU 不會(huì)并發(fā)讀寫同一塊內(nèi)存浪谴,進(jìn)而減少 CPU L1 cache 的 cacheline 出現(xiàn) dirty 情況开睡,增大 cpu cache 命中率。
  3. 內(nèi)存碎片的問題苟耻,Go 是自己在用戶態(tài)管理的篇恒,在 OS 層面看是沒有碎片的,使得操作系統(tǒng)層面對(duì)碎片的管理壓力也會(huì)降低凶杖。
  4. 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 中情況:

  1. 頻繁申請(qǐng)大對(duì)象馆铁,常見于文本處理,比如寫一個(gè)海量日志分析的服務(wù)痪宰,很多日志內(nèi)容都很長叼架。這種情況建議自己維護(hù)一個(gè)對(duì)象([]byte)池畔裕,避免每次都要去 mheap 上分配。
  2. 濫用指針乖订,指針的存在不僅容易造成內(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
        }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市仲闽,隨后出現(xiàn)的幾起案子脑溢,更是在濱河造成了極大的恐慌,老刑警劉巖赖欣,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件屑彻,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡顶吮,警方通過查閱死者的電腦和手機(jī)社牲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來悴了,“玉大人搏恤,你說我怎么就攤上這事∨冉唬” “怎么了熟空?”我有些...
    開封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長搞莺。 經(jīng)常有香客問我息罗,道長,這世上最難降的妖魔是什么才沧? 我笑而不...
    開封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任迈喉,我火速辦了婚禮绍刮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘挨摸。我一直安慰自己孩革,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開白布油坝。 她就那樣靜靜地躺著嫉戚,像睡著了一般。 火紅的嫁衣襯著肌膚如雪澈圈。 梳的紋絲不亂的頭發(fā)上彬檀,一...
    開封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音瞬女,去河邊找鬼窍帝。 笑死,一個(gè)胖子當(dāng)著我的面吹牛诽偷,可吹牛的內(nèi)容都是我干的坤学。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼报慕,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼深浮!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起眠冈,我...
    開封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤飞苇,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后蜗顽,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體布卡,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年雇盖,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了忿等。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡崔挖,死狀恐怖贸街,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情狸相,我是刑警寧澤匾浪,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站卷哩,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏属拾。R本人自食惡果不足惜将谊,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一冷溶、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧尊浓,春花似錦逞频、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至瓦堵,卻和暖如春基协,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背菇用。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來泰國打工澜驮, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人惋鸥。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓杂穷,卻偏偏與公主長得像,于是被迫代替她去往敵國和親卦绣。 傳聞我的和親對(duì)象是個(gè)殘疾皇子耐量,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • Go語言——內(nèi)存管理 參考: 圖解 TCMalloc Golang 內(nèi)存管理 Go 內(nèi)存管理 問題 內(nèi)存碎片:避免...
    陳先生_9e91閱讀 4,797評(píng)論 1 10
  • GO語言內(nèi)存管理子系統(tǒng)主要由兩部分組成:內(nèi)存分配器和垃圾回收器(gc)。內(nèi)存分配器主要解決小對(duì)象的分配管理和多線程...
    adrian920閱讀 11,175評(píng)論 4 6
  • 參考連接: https://www.cnblogs.com/xumaojun/p/8547439.html htt...
    坤_7a1e閱讀 3,107評(píng)論 0 6
  • 內(nèi)存管理 簡述OC中內(nèi)存管理機(jī)制滤港。與retain配對(duì)使用的方法是dealloc還是release廊蜒,為什么?需要與a...
    丶逐漸閱讀 1,948評(píng)論 1 16
  • 1. 基礎(chǔ)知識(shí) 1.1蜗搔、 基本概念劲藐、 功能 馮諾伊曼體系結(jié)構(gòu)1、計(jì)算機(jī)處理的數(shù)據(jù)和指令一律用二進(jìn)制數(shù)表示2樟凄、順序執(zhí)...
    yunpiao閱讀 5,253評(píng)論 1 22