golang gc介绍

1.什么是 GC

在计算机科学中,垃圾回收(GC)是一种自动管理内存的机制,垃圾回收器会去尝试回收程序不再使用的对象及其占用的内存。

最早 John McCarthy 在 1959 年左右发明了垃圾回收,以简化 Lisp 中的手动内存管理的机制(来自 @wikipedia)。

2.为什么要 GC

手动管理内存挺麻烦,管错或者管漏内存也很糟糕,将会直接导致程序不稳定(持续泄露)甚至直接崩溃。

3. GC 触发场景

GC 触发的场景主要分为两大类,分别是:

  1. 系统触发:运行时自行根据内置的条件,检查、发现到,则进行 GC 处理,维护整个应用程序的可用性。

  2. 手动触发:开发者在业务代码中自行调用 runtime.GC 方法来触发 GC 行为。

3.1.系统触发

在系统触发的场景中,Go 源码的 src/runtime/mgc.go 文件,明确标识了 GC 系统触发的三种场景,分别如下:

const (

     gcTriggerHeap gcTriggerKind = iota

     gcTriggerTime

     gcTriggerCycle

)
  • gcTriggerHeap:当所分配的堆大小达到阈值(由控制器计算的触发堆的大小)时,将会触发。

  • gcTriggerTime:当距离上一个 GC 周期的时间超过一定时间时,将会触发。-时间周期以 runtime.forcegcperiod 变量为准,默认 2 分钟。

  • gcTriggerCycle:如果没有开启 GC,则启动 GC。

  • 在手动触发的 runtime.GC 方法中涉及。

3.2. 手动触发

在手动触发的场景下,Go 语言中仅有 runtime.GC 方法可以触发,也就没什么额外的分类的。

runtime.GC()

但我们要思考的是,一般我们在什么业务场景中,要涉及到手动干涉 GC,强制触发他呢?

需要手动强制触发的场景极其少见,可能会是在某些业务方法执行完后,因其占用了过多的内存,需要人为释放。又或是 debug 程序所需

4. GC 实例

4.1.系统触发

package main

import (
	"log"
	"runtime"
	"time"
)

var lastTotalFreed uint64
var intMap map[int]int
var cnt = 8192

func printMemStats(){
	var m runtime.MemStats
	runtime.ReadMemStats(&m)
	log.Printf("Alloc = %v TotalAlloc=%v Just Freed = %v Sys = %v NumGc=%v\n",
		m.Alloc/1024, m.TotalAlloc/1024, ((m.TotalAlloc - m.Alloc)-lastTotalFreed)/1024, m.Sys/1024, m.NumGC)
	lastTotalFreed = m.TotalAlloc - m.Alloc
}

func initMap(){
	intMap = make(map[int]int, cnt)

	for i:=0; i<cnt;i++{
		intMap[i]= i
	}
}

func main() {
	printMemStats()

	initMap()
	runtime.GC()
	printMemStats()

	log.Println(len(intMap))

	for i:=0; i<cnt; i++{
		delete(intMap, i)
	}
	log.Println(len(intMap))

	runtime.GC()
	printMemStats()

	intMap = nil
	// 等待2分钟自动gc
	for {
		time.Sleep(1 * time.Minute)
		printMemStats()
	}
}
3分钟左右内存就释放了
 GOROOT=/usr/local/go #gosetup
GOPATH=/Users/qicycle/Go #gosetup
/usr/local/go/bin/go build -o /private/var/folders/26/ynhz7g5n3xg19q_bnpqcjpdc0000gn/T/___go_build_main_gc_go -gcflags all=-N -l /System/Volumes/Data/Users/qicycle/Documents/我的测试/golang/main_gc.gosetup
/Applications/GoLand.app/Contents/plugins/go/lib/dlv/mac/dlv --listen=0.0.0.0:51694 --headless=true --api-version=2 --check-go-version=false --only-same-user=false exec /private/var/folders/26/ynhz7g5n3xg19q_bnpqcjpdc0000gn/T/___go_build_main_gc_go --
API server listening at: [::]:51694
debugserver-@(#)PROGRAM:LLDB  PROJECT:lldb-1100.0.30..1
 for x86_64.
Got a connection, launched process /private/var/folders/26/ynhz7g5n3xg19q_bnpqcjpdc0000gn/T/___go_build_main_gc_go (pid = 3497).
2023/02/07 14:17:44 Alloc = 66 TotalAlloc=66 Just Freed = 0 Sys = 12143 NumGc=0
2023/02/07 14:17:44 Alloc = 380 TotalAlloc=383 Just Freed = 3 Sys = 12719 NumGc=1
2023/02/07 14:17:44 8192
2023/02/07 14:17:44 0
2023/02/07 14:17:44 Alloc = 380 TotalAlloc=384 Just Freed = 1 Sys = 12783 NumGc=2
2023/02/07 14:18:44 Alloc = 381 TotalAlloc=385 Just Freed = 0 Sys = 12783 NumGc=2
2023/02/07 14:19:44 Alloc = 381 TotalAlloc=385 Just Freed = 0 Sys = 12783 NumGc=2
2023/02/07 14:20:44 Alloc = 68 TotalAlloc=386 Just Freed = 313 Sys = 12783 NumGc=3
Exiting.

可以看到大概等待3分钟左右,系统就自动释放了临时申请的内存:

14:17:44的时候Alloc是66,14:20:44时候Alloc是68

4.2. 手动触发

package main

import (
	"log"
	"runtime"
)

var lastTotalFreed uint64
var intMap map[int]int
var cnt = 8192

func printMemStats(){
	var m runtime.MemStats
	runtime.ReadMemStats(&m)
	log.Printf("Alloc = %v TotalAlloc=%v Just Freed = %v Sys = %v NumGc=%v\n",
		m.Alloc/1024, m.TotalAlloc/1024, ((m.TotalAlloc - m.Alloc)-lastTotalFreed)/1024, m.Sys/1024, m.NumGC)
	lastTotalFreed = m.TotalAlloc - m.Alloc
}

func initMap(){
	intMap = make(map[int]int, cnt)

	for i:=0; i<cnt;i++{
		intMap[i]= i
	}
}

func main() {
	printMemStats()

	initMap()
	runtime.GC()
	printMemStats()

	log.Println(len(intMap))

	for i:=0; i<cnt; i++{
		delete(intMap, i)
	}
	log.Println(len(intMap))

	runtime.GC()
	printMemStats()

	intMap = nil

	// 手动gc
	runtime.GC()
	printMemStats()
}
手动gc立刻释放内存
 /usr/local/go/bin/go build -o /private/var/folders/26/ynhz7g5n3xg19q_bnpqcjpdc0000gn/T/___go_build_main_gc_go -gcflags all=-N -l /System/Volumes/Data/Users/qicycle/Documents/我的测试/golang/main_gc.gosetup
/Applications/GoLand.app/Contents/plugins/go/lib/dlv/mac/dlv --listen=0.0.0.0:51917 --headless=true --api-version=2 --check-go-version=false --only-same-user=false exec /private/var/folders/26/ynhz7g5n3xg19q_bnpqcjpdc0000gn/T/___go_build_main_gc_go --
API server listening at: [::]:51917
debugserver-@(#)PROGRAM:LLDB  PROJECT:lldb-1100.0.30..1
 for x86_64.
Got a connection, launched process /private/var/folders/26/ynhz7g5n3xg19q_bnpqcjpdc0000gn/T/___go_build_main_gc_go (pid = 3664).
2023/02/07 14:34:48 Alloc = 68 TotalAlloc=68 Just Freed = 0 Sys = 12399 NumGc=0
2023/02/07 14:34:48 Alloc = 382 TotalAlloc=385 Just Freed = 3 Sys = 12719 NumGc=1
2023/02/07 14:34:48 8192
2023/02/07 14:34:48 0
2023/02/07 14:34:48 Alloc = 382 TotalAlloc=386 Just Freed = 1 Sys = 12783 NumGc=2
2023/02/07 14:34:48 Alloc = 69 TotalAlloc=387 Just Freed = 313 Sys = 13039 NumGc=3
Exiting.

 

可以看到gc以后内存立刻释放了:

14:34:48的时候Alloc是68,14:34:48最后一条时候Alloc是69

 

结论:需要手动强制触发的场景极其少见,可能会是在某些业务方法执行完后,因其占用了过多的内存,需要人为释放。又或是 debug 程序所需

posted @ 2023-02-07 14:37  若-飞  阅读(314)  评论(0编辑  收藏  举报