1.什么是 GC
在计算机科学中,垃圾回收(GC)是一种自动管理内存的机制,垃圾回收器会去尝试回收程序不再使用的对象及其占用的内存。
最早 John McCarthy 在 1959 年左右发明了垃圾回收,以简化 Lisp 中的手动内存管理的机制(来自 @wikipedia)。
2.为什么要 GC
手动管理内存挺麻烦,管错或者管漏内存也很糟糕,将会直接导致程序不稳定(持续泄露)甚至直接崩溃。
3. GC 触发场景
GC 触发的场景主要分为两大类,分别是:
-
系统触发:运行时自行根据内置的条件,检查、发现到,则进行 GC 处理,维护整个应用程序的可用性。
-
手动触发:开发者在业务代码中自行调用
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 程序所需
标签:触发,Alloc,14,介绍,golang,TotalAlloc,gc,go,GC From: https://www.cnblogs.com/zhanchenjin/p/17098294.html