内存管理
Go 使用自动垃圾回收(GC)管理内存。本章将详细介绍 Go 的内存管理机制。
堆和栈
栈内存
go
func stackExample() {
// 局部变量分配在栈上
x := 10
y := 20
sum := x + y
// 函数返回后自动释放
}
堆内存
go
func heapExample() *int {
// 返回局部变量的指针,分配在堆上
x := 10
return &x
}
垃圾回收
GC 基础
Go 使用三色标记清除算法进行垃圾回收。
go
func gcExample() {
// 创建对象
obj := &MyStruct{}
// 使用对象
obj.Method()
// obj 不再被引用,GC 会回收
}
GC 调优
go
func main() {
// 设置 GC 目标百分比
debug.SetGCPercent(100)
// 手动触发 GC
runtime.GC()
// 你的代码...
}
内存泄漏
Goroutine 泄漏
go
// 错误示例:goroutine 泄漏
func leak() {
ch := make(chan int)
go func() {
<-ch // 永远阻塞
}()
// ch 永远不会被关闭
}
// 正确示例
func noLeak() {
ch := make(chan int)
go func() {
select {
case <-ch:
case <-time.After(time.Second):
}
}()
close(ch)
}
循环引用
go
type Node struct {
Next *Node
Data interface{}
}
// 错误示例:循环引用
func createCycle() {
a := &Node{Data: "A"}
b := &Node{Data: "B"}
a.Next = b
b.Next = a // 循环引用
// 需要手动打破循环
a.Next = nil
b.Next = nil
}
全局变量
go
// 错误示例:全局变量累积
var cache = make(map[string]interface{})
func addToCache(key string, value interface{}) {
cache[key] = value // 永远增长
}
// 正确示例:使用 LRU
type LRUCache struct {
capacity int
items map[string]*list.Element
lru *list.List
}
func NewLRUCache(capacity int) *LRUCache {
return &LRUCache{
capacity: capacity,
items: make(map[string]*list.Element),
lru: list.New(),
}
}
内存优化技巧
减少分配
go
// 不推荐:频繁分配
func process(data []byte) {
for i := 0; i < 1000; i++ {
buf := make([]byte, 1024)
copy(buf, data)
// 处理...
}
}
// 推荐:重用缓冲区
func processOptimized(data []byte) {
buf := make([]byte, 1024)
for i := 0; i < 1000; i++ {
copy(buf, data)
// 处理...
}
}
对象池
go
var objectPool = sync.Pool{
New: func() interface{} {
return &MyStruct{}
},
}
func getObject() *MyStruct {
return objectPool.Get().(*MyStruct)
}
func putObject(obj *MyStruct) {
obj.Reset()
objectPool.Put(obj)
}
预分配
go
// 不推荐:动态增长
func createSlice(n int) []int {
slice := []int{}
for i := 0; i < n; i++ {
slice = append(slice, i)
}
return slice
}
// 推荐:预分配
func createSliceOptimized(n int) []int {
slice := make([]int, n)
for i := 0; i < n; i++ {
slice[i] = i
}
return slice
}
内存分析
pprof 内存分析
bash
# 采集堆数据
go tool pprof http://localhost:6060/debug/pprof/heap
# 查看分配
(pprof) top
# 查看特定函数
(pprof) list functionName
# 生成火焰图
(pprof) web
内存统计
go
func printMemStats() {
var m runtime.MemStats
runtime.ReadMemStats(&m)
fmt.Printf("Alloc = %v MiB\n", m.Alloc/1024/1024)
fmt.Printf("TotalAlloc = %v MiB\n", m.TotalAlloc/1024/1024)
fmt.Printf("Sys = %v MiB\n", m.Sys/1024/1024)
fmt.Printf("NumGC = %v\n", m.NumGC)
}
内存对齐
结构体对齐
go
// 不推荐:浪费内存
type BadStruct struct {
a bool // 1 byte
b int64 // 8 bytes
c bool // 1 byte
d int64 // 8 bytes
}
// 总大小: 32 bytes
// 推荐:优化对齐
type GoodStruct struct {
b int64 // 8 bytes
d int64 // 8 bytes
a bool // 1 byte
c bool // 1 byte
}
// 总大小: 16 bytes
检查对齐
go
func printStructSize() {
fmt.Printf("BadStruct: %d bytes\n", unsafe.Sizeof(BadStruct{}))
fmt.Printf("GoodStruct: %d bytes\n", unsafe.Sizeof(GoodStruct{}))
}
内存最佳实践
1. 避免不必要的指针
go
// 不推荐:使用指针
type Data struct {
value *int
}
// 推荐:使用值
type DataOptimized struct {
value int
}
2. 使用值类型
go
// 不推荐:切片的切片
type Matrix [][]float64
// 推荐:一维数组
type MatrixOptimized struct {
data []float64
rows int
cols int
}
3. 及时释放资源
go
func processFile(filename string) error {
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close() // 确保释放
// 处理文件...
return nil
}
内存泄漏检测
使用 pprof
bash
# 对比两个时间点的内存
go tool pprof -base=base.prof current.prof
使用 runtime
go
func checkGoroutines() {
fmt.Printf("Goroutines: %d\n", runtime.NumGoroutine())
}
func checkMemory() {
var m runtime.MemStats
runtime.ReadMemStats(&m)
fmt.Printf("Alloc: %d MB\n", m.Alloc/1024/1024)
}
总结
- 理解堆和栈的区别
- 了解 GC 的工作原理
- 避免常见的内存泄漏
- 使用对象池减少分配
- 优化结构体对齐
- 定期分析内存使用
下一章:CGO