Go源碼閱讀——mstats.go

【博文目錄>>>】 【項目地址>>>】


Go內存統計

Go提供了運行進內存統計的方法,其實現在runtime/mstats.go文件中。go內存統計的作用有:

  • Go內存實時監控
  • 內存dump時,統計內存相關信息
  • Go GC時,統計GC相關的信息
  • 應用調試時分析內存分配等

Go中的內存統計信息結構有兩個,分別是mstats和MemStats,詳細信息如下:

mstats

// 統計信息。
// 如果您編輯此結構,請同時在下面編輯MemStats類型。它們的佈局必須完全匹配。
//
// 有關詳細說明,請參閱MemStats文檔。與MemStats不同的字段將在此處進一步記錄。
//
// 這些字段中的許多都是動態更新的,而其他字段僅在調用updatememstats時才更新。
type mstats struct {
	// 一般統計。
	alloc       uint64 // 已分配但尚未釋放的字節
	total_alloc uint64 // 分配的字節(即使已釋放)
	sys         uint64 // 從系統獲得的字節(應爲以下xxx_sys的總和,無鎖定,近似值)
	nlookup     uint64 // 指針查找數(未使用)
	nmalloc     uint64 // malloc的次數
	nfree       uint64 // 釋放的次數

    // 有關malloc堆的統計信息。
    // 原子更新,或者全局停頓。
    //
    // 與MemStats一樣,heap_sys和heap_inuse不會在手動管理的範圍內計算內存。
	heap_alloc    uint64 // 已分配但尚未釋放的字節(與上面的alloc相同)
	heap_sys      uint64 // 從系統獲得的用於GC堆的虛擬地址空間
	heap_idle     uint64 // 空閒跨度(span)中的字節
	heap_inuse    uint64 // mSpanInUse跨度(span)中的字節
	heap_released uint64 // 釋放到操作系統的字節

    // 運行時不直接使用heap_objects,而是由updatememstats動態計算。
	heap_objects uint64 // 分配的對象總數

    // 有關低級別固定大小結構分配的統計信息。
    // 受FixAlloc鎖保護。
	stacks_inuse uint64 // 手動管理的堆棧跨度中的字節; 自動更新或在STW期間更新
	stacks_sys   uint64 // 僅在mstats中計算newosproc0堆棧; 與MemStats.StackSys不同
	mspan_inuse  uint64 // mspan結構
	mspan_sys    uint64
	mcache_inuse uint64 // mcache結構
	mcache_sys   uint64
	buckhash_sys uint64 // 分析存儲桶哈希表
	gc_sys       uint64 // 自動更新或在STW期間更新
	other_sys    uint64 // 自動更新或在STW期間更新

    // 有關垃圾收集器的統計信息。
    //在GC期間受mheap保護或全局停機。
	next_gc         uint64 // 下一個GC何時結束的目標heap_live; ^0(如果禁用)
	last_gc_unix    uint64 // 最後一次gc(Unix時間)
	pause_total_ns  uint64
	pause_ns        [256]uint64 // 最近gc暫停長度的循環緩衝區
	pause_end       [256]uint64 // 最近gc結束時間的循環緩衝區(自1970年以來的納秒)
	numgc           uint32
	numforcedgc     uint32  // 用戶強制GC的數量
	gc_cpu_fraction float64 // GC使用的CPU時間的一部分
	enablegc        bool
	debuggc         bool

    // 有關分配大小類的統計信息。

	by_size [_NumSizeClasses]struct {
		size    uint32
		nmalloc uint64
		nfree   uint64
	}

    // 下面的統計信息不會直接導出到MemStats。

	last_gc_nanotime uint64 // 最後gc(單調時間)
	tinyallocs       uint64 // 不會導致實際分配的微小分配的數量; 沒有直接導出到go
	last_next_gc     uint64 // 上一個GC:next_gc
	last_heap_inuse  uint64 // 前一個GC的標記終止處的heap_inuse

    // triggerRatio是觸發標記的堆增長比率。
    //
    // 例如,如果該值爲0.6,則當活動堆達到上一個週期標記的堆大小的1.6倍時,GC應該啓動。
    // 該值應≤GOGC/100,以便觸發器堆大小小於目標堆大小。這是在標記終止期間爲下一個循環的觸發設置的。
	triggerRatio float64

    //
    // gc_trigger是觸發標記的堆大小。
    //
    // 當heap_live≥gc_trigger時,標記階段將開始。
    // 這也是必須完成比例掃描的堆大小。
    //
    // 這是在標記終止期間爲下一個循環的觸發器從triggerRatio計算的。
	gc_trigger uint64

    // heap_live是GC認爲存活的字節數。
    // 即:由最近的GC保留,此後再分配。 heap_live <= heap_alloc,
    // 因爲heap_alloc包括尚未被清除的未標記對象(因此在我們分配時上升,而在掃描時下降),
    // 而heap_live排除這些對象(因此僅在GC之間上升)。
    //
    // 這是自動更新的,沒有鎖定。爲了減少爭用,僅當從一個mcentrol獲得一個跨度(span)時才更新該爭用,
    // 此時,它會計算該跨度中所有未分配的槽位(在該mcache從該mcentrol獲取另一個跨度之前將對其進行分配)。
    // 因此,它稍微高估了“真實”活動堆的大小。最好高估而不是低估,因爲
    //  1)觸發GC的時間比必要的要早,而不是可能太晚; 
    //  2)導致保守的GC速率,而不是可能太低的GC速率。
    //
    // 讀取應該同樣是原子的(或在STW期間)。
    //
    // 每當更新時,都調用traceHeapAlloc()和gcController.revise()。
	heap_live uint64

    // heap_scan是“可掃描”堆的字節數。這是活動堆(由heap_live計算),
    // 但是省略了非掃描對象和對象的非掃描尾部。
    //
    // 每當更新時,都調用gcController.revise()。
	heap_scan uint64

	// heap_marked是前一個GC標記的字節數。標記終止後,heap_live == heap_marked, 
	// 但是與heap_live不同,heap_marked直到下一個標記終止才更改。
	heap_marked uint64
}

MemStats

// MemStats記錄有關內存分配器的統計信息。
type MemStats struct {
    // 常規統計信息。

    // Alloc是分配的堆對象的字節。
    //
    // 這與HeapAlloc相同(請參見下文)。
	Alloc uint64

    // TotalAlloc是分配給堆對象的累積字節。
    //
    // TotalAlloc隨着分配堆對象而增加,但是與Alloc和HeapAlloc不同,釋放對象時它不會減少。
	TotalAlloc uint64

    // Sys是從OS獲得的內存的總字節數。
    //
    // Sys是以下XSys字段的總和。 Sys衡量Go運行時爲堆,棧和其他內部數據結構保留的虛擬地址空間。
    // 在任何給定時刻,可能並非所有虛擬地址空間都由物理內存支持,儘管通常情況下,所有虛擬地址空間都在某個時刻。
	Sys uint64

    // Lookups是運行時執行的指針查找的次數。
    //
    // 這主要用於調試運行時內部。
	Lookups uint64

    // Mallocs是分配的堆對象的累積計數。
    // 活動對象的數量爲Malloc-Frees。
	Mallocs uint64

    // Frees是已釋放的堆對象的累積計數。
	Frees uint64

    // 堆內存統計信息。
    //
    // 解釋堆統計信息需要了解Go如何組織內存。 Go將堆的虛擬地址空間劃分爲跨度(“span”),
    // 即內存8K或更大的連續區域。跨度可能處於以下三種狀態之一:
    //
    // “空閒”跨度(span)不包含任何對象或其他數據。支持空閒範圍的物理內存可以釋放回操作系統
    // (但虛擬地址空間永遠不會釋放),也可以將其轉換爲“使用中”或“堆棧”跨度(span)。
    //
    // 一個“使用中”的跨度至少包含一個堆對象,並且可能具有可用空間來分配更多的堆對象。
    //
    // “堆棧”跨度用於goroutine堆棧。堆棧跨度不視爲堆的一部分。跨度可以在堆和堆棧內存之間改變;永遠不會同時使用它們。
    // 
    // HeapAlloc是分配的堆對象的字節。
    //
    // “已分配”堆對象包括所有可訪問對象,以及垃圾回收器尚未釋放的不可訪問對象。
    // 具體來說,HeapAlloc隨着分配堆對象而增加,而隨着堆被清除而無法訪問的對象被釋放而減少。
    // 清除過程在GC週期之間逐漸發生,因此這兩個過程同時發生,因此HeapAlloc趨於平穩變化
    // (與全局停機的垃圾收集器所採用的典型鋸齒形成對比)。
	HeapAlloc uint64

    // HeapSys是從操作系統獲得的堆內存字節。
    //
    // HeapSys測量爲堆保留的虛擬地址空間量。這包括已保留但尚未使用的虛擬地址空間,
    // 該虛擬地址空間不佔用物理內存,但往往很小,以及在物理內存變得不使用後已將其
    // 返回給操作系統的虛擬地址空間(請參閱HeapReleased以衡量後者)。
    //
    // HeapSys估計堆具有的最大大小。
	HeapSys uint64

    // HeapIdle是空閒(未使用)跨度中的字節。
    //
    // 空閒跨度中沒有對象。這些範圍可以(並且可能已經)返回到操作系統,
    // 或者可以重新用於堆分配,或者可以重新用作棧內存。
    //
    // HeapIdle減去HeapReleased估計可以返回給OS的內存量,但是該內存將由運行時保留,
    // 因此它可以增長堆而無需從OS請求更多內存。如果此差異明顯大於堆大小,
    // 則表明活動堆大小最近出現了短暫的峯值。
	HeapIdle uint64

    // HeapInuse是使用中的跨度中的字節。
    //
    // 使用中的跨度中至少包含一個對象。這些跨度只能用於大小大致相同的其他對象。
    //
    // HeapInuse減去HeapAlloc估計專用於特定大小類別的內存量,但當前未使用。這是碎片的上限,但是通常可以有效地重用此內存。
	HeapInuse uint64

    // HeapReleased是返回操作系統的物理內存字節。
    //
    // 這將從返回到OS且尚未爲堆重新獲取的span範圍中計算堆內存。
	HeapReleased uint64

    // HeapObjects是分配的堆對象的數量。
    //
    // 像HeapAlloc一樣,隨着分配對象的增加,它會增加;而隨着堆的清除和無法訪問的對象的釋放,這會減少。
	HeapObjects uint64

    // 棧內存統計信息。
    //
    // 棧不被視爲堆的一部分,但是運行時可以將堆內存的一部分重用於棧內存,反之亦然。

    // StackInuse是棧跨度中的字節。
    //
    // 使用中的棧跨度中至少包含一個棧。這些跨度只能用於相同大小的其他棧。
    //
    // 沒有StackIdle,因爲未使用的棧範圍返回到堆中(因此計入HeapIdle)
	StackInuse uint64

    // StackSys是從OS獲得的棧內存的字節。
    //
    // StackSys是StackInuse,再加上直接從OS獲取的用於OS線程棧的任何內存(應該很少)。
	StackSys uint64

    // 堆外內存統計信息。
    //
    // 以下統計信息將度量未從堆內存分配的運行時內部結構(通常是因爲它們是實現堆的一部分)。
    // 與堆或棧內存不同,分配給這些結構的任何內存都專用於這些結構。
    //
    // 這些主要用於調試運行時內存開銷。

    // MSpanInuse是分配的mspan結構的字節。
	MSpanInuse uint64

    // MSpanSys是從操作系統獲取的用於mspan結構的內存字節。
	MSpanSys uint64

    // MCacheInuse是分配的mcache結構的字節。
	MCacheInuse uint64

    // MCacheSys是從操作系統獲取的用於mcache結構的內存字節。
	MCacheSys uint64

    // BuckHashSys是分析存儲桶哈希表中的內存字節。
	BuckHashSys uint64

    // GCSys是垃圾回收元數據中的內存字節。
	GCSys uint64

    // OtherSys是其他堆外運行時分配中的內存字節。
	OtherSys uint64

	// Garbage collector statistics.
    // 垃圾收集器統計信息。

    // NextGC是下一個GC週期的目標堆大小。
    //
    // 垃圾收集器的目標是保持HeapAlloc≤NextGC。
    // 在每個GC週期結束時,根據可獲得的數據量和GOGC的值計算下一個週期的目標。
	NextGC uint64

    // LastGC是最後一次垃圾回收完成的時間,自1970年(UNIX時代)以來,以納秒爲單位。
	LastGC uint64

    // PauseTotalNs是自程序啓動以來,GC全局停機暫停的累積納秒。
    //
    // 在全局停機暫停期間,所有goroutine都將暫停並且只有垃圾收集器可以運行。
	PauseTotalNs uint64

    // PauseNs是最近GC全局停機暫停時間(以納秒爲單位)的循環緩衝區。
    //
    // 最近的暫停是在PauseNs[(NumGC+255)%56]處。通常,PauseNs[N%256]記錄最近的第N%256個GC週期中暫停的時間。
    // 每個GC週期可能會有多個暫停;這是一個週期中所有暫停的總和。
	PauseNs [256]uint64

    // PauseEnd是最近的GC暫停結束時間的循環緩衝區,自1970年(UNIX時代)以來爲納秒。
    //
    // 此緩衝區的填充方式與PauseNs相同。每個GC週期可能會有多個暫停;這將記錄一個週期中最後一個暫停的結束。
	PauseEnd [256]uint64

    // NumGC是已完成的GC週期數。
	NumGC uint32

    // NumForcedGC是應用程序調用GC函數強制執行的GC週期數。
	NumForcedGC uint32

    // 自程序啓動以來,GCCPUFraction是該程序使用的該程序可用CPU時間的一部分。
    //
    // GCCPUFraction表示爲0到1之間的數字,其中0表示GC沒有消耗該程序的CPU。
    // 自程序啓動以來,程序的可用CPU時間定義爲GOMAXPROCS的整數。也就是說,
    // 如果GOMAXPROCS爲2且程序已運行10秒鐘,則其“可用CPU”爲20秒鐘。 
    //  GCCPUFraction不包括用於寫屏障活動的CPU時間。
    //
    // 這與GODEBUG = gctrace = 1報告的CPU分數相同。
	GCCPUFraction float64

    // EnableGC表示已啓用GC。即使GOGC = off,也總是如此
	EnableGC bool

    // // DebugGC當前未使用。
	DebugGC bool

    // BySize報告按大小分類分配的統計信息。
    //
    // BySize [N]給出大小爲S的分配的統計信息,其中
    // BySize [N-1] .Size <S≤BySize [N] .Size。
    //
    // 這不會報告大於BySize [60] .Size的分配。
	BySize [61]struct {
        // Size是此size類中對象的最大字節大小。
		Size uint32

        // Mallocs是在此size類中分配的堆對象的累積計數。分配的累積字節爲Size * Mallocs。
        // 此大小類中的活動對象數量爲Mallocs-Frees。
		Mallocs uint64

        // Frees是在此size類中釋放的堆對象的累積計數。
		Frees uint64
	}
}

mstats和MemStats的區別有:

  • mstats供內部使用,MemStats供外部使用
  • mstats和MemStats的字段大部分是對應的,從by_size(BySize)開始有差別。

在這裏插入圖片描述

使用

type Garbage struct{ a int }

func notify(f *Garbage) {
	stats := &runtime.MemStats{}
	runtime.ReadMemStats(stats)

	bytes, _ := json.MarshalIndent(stats, "", "    ")
	fmt.Println(string(bytes))

	go ProduceFinalizedGarbage()
}

func ProduceFinalizedGarbage() {
	x := &Garbage{}
	runtime.SetFinalizer(x, notify)
}

func main() {
	go ProduceFinalizedGarbage()

	for {
		runtime.GC()
		time.Sleep(10 * time.Second)
	}
}

完整源碼

https://github.com/Wang-Jun-Chao/go-source-read/blob/master/runtime/mstats_go.md

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章