面试题答案
一键面试闭包的内存占用与生命周期关联
- 关联原理:在Go语言中,闭包是一个函数值,它引用了其函数体之外的变量。闭包的生命周期与其引用的外部变量的生命周期紧密相关。当闭包被创建时,它会捕获(引用)外部变量。只要闭包本身还在被使用(例如,闭包函数指针被保存,能被调用),那么它所引用的外部变量就不会被垃圾回收(GC),因为闭包形成了对这些外部变量的引用关系。这意味着闭包的内存占用(包括闭包函数本身以及它所引用的外部变量)会持续存在,直到闭包不再被任何地方引用。
- 具体分析:从内存角度看,闭包所引用的外部变量会和闭包函数体一起被分配在堆上(如果闭包的外部变量在栈上创建,但被闭包引用后,会被提升到堆上)。只要闭包的引用存在,垃圾回收器就不能回收这些内存。这保证了闭包在任何时候调用都能访问到正确的外部变量值。
外部变量生命周期结束时闭包内存占用变化
- 变化情况:当闭包引用的外部变量的生命周期在正常逻辑下看似结束(例如,外部变量所在的函数返回),但由于闭包对其引用,该外部变量不会立即被销毁。只有当闭包也不再被任何地方引用时,垃圾回收器才会回收闭包以及它所引用的外部变量占用的内存。
- 原因:Go语言的垃圾回收机制基于可达性分析。如果一个对象(包括闭包和它引用的外部变量)从根对象(例如全局变量、当前正在执行函数的栈上变量等)可达,那么它就不会被回收。闭包对外部变量的引用使得外部变量从闭包这个可达对象可达,所以外部变量不会因常规的作用域结束而被回收。
代码示例
package main
import "fmt"
func outer() func() int {
var x int
// 内部函数形成闭包,引用了外部变量x
inner := func() int {
x++
return x
}
return inner
}
func main() {
// 获取闭包实例
f := outer()
// 调用闭包,此时外部函数outer已经返回,但x不会被回收
fmt.Println(f()) // 输出1
fmt.Println(f()) // 输出2
}
在上述代码中,outer
函数返回一个闭包inner
。inner
闭包引用了outer
函数中的局部变量x
。当outer
函数返回后,x
从常规的函数作用域角度看生命周期结束了,但由于闭包inner
对其引用,x
不会被回收。每次调用闭包f
,都会修改并返回x
的值,这表明x
一直存在于内存中,直到闭包f
不再被引用,垃圾回收器才会回收x
以及闭包占用的内存。