Go 中的垃圾回收
本课属于 Coddy GO 学习旅程的“面向对象编程”单元。第 23 课,共 107 课。
与 C 或 C++ 等语言不同,Go 通过垃圾回收(GC)自动管理内存。内存用完后,你不需要手动释放。Go 运行时会跟踪仍在使用的内存,并回收其余内存。
使用 new()、make() 分配内存或创建变量时,Go 的垃圾回收器会监控这些分配。一旦程序的任何部分都无法再访问某个值,GC 就会自动释放这部分内存。
func createPerson() *Person {
p := &Person{Name: "Alice", Age: 30}
return p // p 会逃逸到堆上,由 GC 管理
}
func main() {
person := createPerson()
fmt.Println(person.Name)
// 不再使用 person 时,GC 会回收内存
}在这个示例中,Person 结构体分配在堆上,因为它是从函数中返回的。Go 编译器会执行逃逸分析,以确定变量可以留在栈上,还是必须移到堆上。垃圾回收器只管理堆内存。
这种自动内存管理意味着,你可以专注于构建应用程序,而不必担心忘记释放内存导致的内存泄漏。不过,了解 GC 的存在有助于你在性能重要时减少不必要的内存分配,从而编写更高效的代码。
挑战
简单让我们构建一个会话管理器,演示 Go 的垃圾回收器如何处理逃逸到堆上的对象内存。你将创建动态分配并从函数中返回的会话,让 GC 管理它们的生命周期。
你将把代码分成两个文件:
session.go:定义一个Session结构体,包含ID(字符串)、Username(字符串)和Data(字符串切片)字段。创建一个NewSession函数,接收 ID 和用户名,通过返回指针在堆上分配一个新的 Session,并将 Data 切片初始化为空。再添加一个带指针接收者的AddData方法,用于向会话的 Data 切片追加一个字符串;并添加一个Summary方法,返回显示会话详细信息的格式化字符串。main.go:从输入中读取会话信息,使用构造函数创建会话,向其中添加一些数据条目,并显示会话摘要。由于会话是在函数内部创建并作为指针返回的,因此它会逃逸到堆上,由垃圾回收器管理。
输入内容如下:
- 第 1 行:会话 ID
- 第 2 行:用户名
- 第 3 行:要添加的第一个数据条目
- 第 4 行:要添加的第二个数据条目
你的 Summary 方法应返回以下格式的字符串:
Session [ID] for user [Username]
Data entries: [count]
- [entry1]
- [entry2]例如,输入 sess-001、alice、login 和 view_dashboard 时,输出应为:
Session sess-001 for user alice
Data entries: 2
- login
- view_dashboard这里的关键在于,NewSession 函数会创建一个 Session 并返回指向它的指针。这会使 Session 逃逸到堆上,而不是留在栈上;当不再有任何引用指向它时,它就可以被垃圾回收。你的代码不需要手动释放这块内存,Go 会自动处理。
亲手试试
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// 读取会话 ID
sessionID, _ := reader.ReadString('\n')
sessionID = sessionID[:len(sessionID)-1]
// 读取用户名
username, _ := reader.ReadString('\n')
username = username[:len(username)-1]
// 读取第一条数据
data1, _ := reader.ReadString('\n')
data1 = data1[:len(data1)-1]
// 读取第二条数据
data2, _ := reader.ReadString('\n')
data2 = data2[:len(data2)-1]
// TODO: 使用 NewSession 函数创建新会话
// 由于返回的是指针,会话会逃逸到堆上
// TODO: 使用 AddData 方法将数据添加到会话中
// TODO: 使用 Summary 方法打印会话摘要
fmt.Println("")
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: Go 在线编译器