前言
我们都知道go语言中内存管理工作都是由Go在底层完成的,这样我们可以不用过多的关注底层的内存问题,有更多的精力去关注业务逻辑, 但掌握内存的管理,理解内存分配机制,可以让你写出更高效的代码,本文主要总结一下 Golang内存逃逸分析,需要的朋友可以参考以下内容,希望对大家有帮助。
什么是内存逃逸
在了解什么是内存逃逸之前,我们先来了解两个概念,栈内存和堆内存。
堆内存(Heap):一般来讲是人为手动进行管理,手动申请、分配、释放。一般硬件内存有多大堆内存就有多大。适合不可预知大小的内存分配,分配速度较慢,而且会形成内存碎片。
栈内存(Stack):是一种拥有特殊规则的线性表数据结构。由编译器进行管理,自动申请、分配、释放。大小一般是固定的。
通过上面我们可以看出堆分配昂贵,栈分配廉价,在go中所有内存优先栈分配,那么,Go 编译器怎么知道某个变量需要分配在栈上,还是堆上呢?
逃逸分析是用于堆和栈分配进行选择,通过在编译时期做gc,编译器追踪变量在代码块的作用域,判断变量在整个运行周期是否在运行时完全可知,通过校验可以在栈上分配;否则逃逸到堆上;逃逸分析由编译器完成,作用于编译阶段。
查看对象是否发生逃逸
Go 语言工具链提供了查看对象是否逃逸的方法,我们在执行 go build 时,配合使用参数 -gcflags 开启编译器支持的额外功能,例如:
-m-m
-l inline
除了使用编译参数之外,我们还可以使用一种更底层的,更硬核,也更准确的方式来判断一个对象是否逃逸,那就是: 通过反编译命令查看
示例:
结果如下,第7行变量 res 逃逸到了堆上
内存逃逸分析的意义
通过逃逸分析,可以尽量把那些不需要分配到堆上的变量直接分配到栈上,堆上的变量少了,会减轻分配堆内存的开销,同时也会减少GC的压力,提高程序的运行速度。
怎么避免内存逃逸
尽量减少外部指针引用,必要的时候可以使用值传递;
对于自己定义的数据大小,有一个基本的预判,尽量不要出现栈空间溢出的情况;
Golang中的接口类型的方法调用是动态调度,如果对于性能要求比较高且访问频次比较高的函数调用,应该尽量避免使用接口类型;
尽量不要写闭包函数,可读性差且发生逃逸。
小结
本文主要介绍了 Go 语言逃逸分析,它可以帮助我们合理分配对象的内存空间。
我们知道分配到堆内存空间的对象,会导致 Go 执行垃圾回收,而垃圾回收会占用系统资源,降低应用程序本身可使用的系统资源。
所以,我们在实际项目开发中,可以借助 Go 工具链分析对象是否会发生逃逸,尽量避免一些不必要的对象逃逸。