内存管理:空间分配及逃逸分析 内存管理: 分配与逃逸分析(4)
内存管理:空间分配及逃逸分析

&User{...} escapes to heap 即表示对象逃逸到堆上了。
2. interface{} 动态类型逃逸
在 Go 语言中,空接口即 interface{} 可以表示任意的类型,如果函数参数为 interface{},编译期间很难确定其参数的具体类型,也会发生逃逸。比如 Println 函数,入参是一个 interface{} 空类型:
func Println(a ...interface{}) (n int, err error)
这时,返回的是一个 User 对象,也会发生对象逃逸,但逃逸节点是 fmt.Println 函数使用时:
func GetUserInfo() User {
return User{
ID: 666666,
Name: "sim lou",
Avatar: "https://www.baidu.com/avatar/666666",
}
}
func main() {
u := GetUserInfo()
fmt.Println(u.Name) // 对象发生逃逸
}
3. 栈空间不足
操作系统对内核线程使用的栈空间是有大小限制的,64 位 Linux 系统上通常是 8 MB。
可以使用 ulimit -a 命令查看机器上栈允许占用的内存的大小。
root@cvm_172_16_10_34:~ # ulimit -a
-s: stack size (kbytes) 8192
-u: processes 655360
-n: file descriptors 655360
因为栈空间通常比较小,因此递归函数实现不当时,容易导致栈溢出。
对于 Go 语言来说,运行时(runtime) 尝试在 goroutine 需要的时候动态地分配栈空间,goroutine 的初始栈大小为 2 KB。当 goroutine 被调度时,会绑定内核线程执行,栈空间大小也不会超过操作系统的限制。
对 Go 编译器而言,超过一定大小的局部变量将逃逸到堆上,不同的 Go 版本的大小限制可能不一样。我们来做一个实验(注意,分配 int[] 时,int 占 8 字节,所以 8192 个 int 就是 64 KB):
package main
import "math/rand"
func generate8191() {
nums := make([]int, 8192) // <= 64KB
for i := 0; i < 8192; i++ {
nums[i] = rand.Int()
}
}
func generate8192() {
nums := make([]int, 8193) // > 64KB
for i := 0; i < 8193; i++ {
nums[i] = rand.Int()
}
}
func generate(n int) {
nums := make([]int, n) // 不确定大小
for i := 0; i < n; i++ {
nums[i] = rand.Int()
}
}
func main() {
generate8191()
generate8192()
generate(1)
}
编译结果如下:

可以发现,make([]int, 8192) 没有发生逃逸,make([]int, 8193) 和 make([]int, n) 逃逸到堆上。
也就是说,当切片占用内存超过一定大小,或无法确定当前切片长度时,对象占用内存将在堆上分配。
4. 闭包
一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。
— 闭包
Go 语言中,当使用闭包函数时,也会发生内存逃逸。看一则示例代码:
package main
func Increase() func() int {
n := 0
return func() int {
n++
return n
}
}
func main() {
in := Increase()
fmt.Println(in()) // 1
fmt.Println(in()) // 2
}
Increase() 函数的返回值是一个闭包函数,该闭包函数访问了外部变量 n,那变量 n 将会一直存在,直到 in 被销毁。很显然,变量 n 占用的内存不能随着函数 Increase() 的退出而回收,因此将会逃逸到堆上。
4.3 利用逃逸分析提升性能
传值VS传指针
传值会拷贝整个对象,而传指针只会拷贝指针地址,指向的对象是同一个。传指针可以减少值的拷贝,但是会导致内存分配逃逸到堆中,增加垃圾回收(GC)的负担。在对象频繁创建和删除的场景下,传递指针导致的 GC 开销可能会严重影响性能。
一般情况下,对于需要修改原对象值,或占用内存比较大的结构体,选择传指针。对于只读的占用内存较小的结构体,直接传值能够获得更好的性能。
5. 小结
内存分配是程序运行时内存管理的核心逻辑,Go 程序运行时的内存分配器使用类似 TCMalloc 的分配策略将对象根据大小分类,并设计多层缓存的组件提高内存分配器的性能。
理解 Go 语言内存分配器的设计与实现原理,可以帮助我们理解不同编程语言在设计内存分配器时做出的不同选择。
栈内存是应用程序中重要的内存空间,它能够支持本地的局部变量和函数调用,栈空间中的变量会与栈一同创建和销毁,这部分内存空间不需要工程师过多的干预和管理,现代的编程语言通过逃逸分析减少了我们的工作量,理解栈空间的分配对于理解 Go 语言的运行时有很大的帮助。
好了,以上就是本文的全部内容了,如果觉得文章有所启发或收获,不妨点赞、分享,这对不念是最大的鼓励!
上述的关于内存管理:空间分配及逃逸分析的详细方法介绍,大家作为一个参考建议。
相关阅读
-
微信摄像头怎么开美颜 设置视频美颜教程
正文核心介绍:微信摄像头怎么开美颜方面的介绍,1、打开手机设置,点击【其他设置】。2、点击【视频通话美颜】。3、打开【微信】右侧的开关,可自主调节美颜效果。
-
笔记本硬盘坏了怎么办 移动硬盘坏了修复方法
本文核心内容:笔记本硬盘坏了怎么办和移动硬盘坏了修复方法的相关经验,很不错的方法小知识,建议收藏哦! 有许多朋友遇到过电脑硬盘出现问题的状况,今天分享修复硬盘坏道的技巧,
-
驱动精灵护眼卸载教程 驱动精灵护眼模式找不到了
一个电脑小知识,为大家介绍驱动精灵护眼卸载教程的内容,关于驱动精灵护眼卸载教程 驱动精灵护眼模式找不到了,接下来一起来看看吧。 不少的IT袋用户下载驱动精灵安装驱动之后都被他
-
怎么连接共享打印机步骤 关于打印机无线连接方法
本文为你介绍怎么连接共享打印机步骤和关于打印机无线连接方法的电脑方面的小经验,接下来IT袋小编就来介绍。 在办公室里打印机多半是通过无线共享的方式进行使用的,那在链接无线打


