这是我参与「第五届青训营 」伴学笔记创作活动的第 3 天
介绍
什么是内存逃逸?
内存逃逸是指原本应该被存储在栈上的变量,因为一些原因被存储到了堆上。
在c/c++中的动态分配的内存,都是需要手动释放的,内存完全由自己操作,但是如果不释放自己申请的内存,因为c/c++是没有gc的,所以就有可能会导致内存泄漏。
GO中的内存逃逸有那些场景
- 变量在函数外部没有引用,优先放到栈中。
- 变量在函数外部存在引用,必定放在堆中
- 发送指针的指针或值包含了指针到 channel 中,由于在编译阶段无法确定其作用域与传递的路径,所以一般都会逃逸到堆上分配。
- 够符合分配到栈的场景,但是其大小不能够在在编译时候确定的情况,也会分配到堆上,参考后看别人测试是超过64k的内存占用放到堆上,但是我自己测试的时候全到堆上面去了,所以这里的大小还不一定。???
查看内存逃逸命令
go build -gcflags="-m -m" main.go
GO中的逃逸分析
逃逸分析是指分析指针动态范围的方法,它同编译器优化原理的指针分析和外形分析相关联。
Go逃逸分析最基本的原则是:如果一个函数返回对一个变量的引用,那么它就会发生逃逸。
任何时候,一个值被分享到函数栈帧范围之外,它都会在堆上被重新分配。
简单来说,编译器会分析代码的特征和代码生命周期,Go中的变量只有在编译器可以证明在函数返回后不会再被引用的,才分配到栈上,其他情况下都是分配到堆上。
Go中的new申请到的内存,如果编译器发现出了函数内部引用到,外部没有引用的话,也是会被分配到栈上面的,毕竟栈上面的速度更快也更方便管理。
所以Go里没有一个关键字或者函数可以直接让变量被编译器分配到堆上,相反,编译器通过分析代码来决定将变量分配到何处。
如何避免或者可以注意哪些方面
GO 中的接口类型的方法调用是动态调度,因此不能够在编译阶段确定,所有类型结构转换成接口的过程会涉及到内存逃逸的情况发生。如果对于性能要求比较高且访问频次比较高的函数调用,应该尽量避免使用接口类型。 例如:
还有一点就是:减少外部引用, 如指针。 不要盲目使用变量的指针作为函数参数,虽然它会减少复制操作。但其实当参数为变量自身的时候,复制是在栈上完成的操作,开销远比变量逃逸后动态地在堆上分配内存少的多。
小结
如果变量都分配到堆上,堆不像栈可以自动清理。它会引起Go频繁地进行垃圾回收,而垃圾回收会占用比较大的系统开销(占用CPU容量的25%),所以尽量减少一些内存逃逸的代码提高系统的效率。
今天准备写大项目中的聊天功能的,学了一下go中怎么用websocket的,写了一个demo,但是这个还没有弄完,所以就等明天把这部分记录一下,然后开始看到内存逃逸,觉得和java中的有点不同(线程逃逸等),感觉是加了gc的c,所以特此记录一下。