这个题是小编面试遇到次数最多的题目之一了。在开始之前,我们先思考以下几个问题,当然,后面小编也会一一解答。 1,什么是内存逃逸。 2,内存逃逸的场景有哪些。 3,分析内存逃逸的意义。 4,怎么避免内存逃逸。 什么是内存逃逸在了解什么是内存逃逸之前,我们先来简单地熟悉一下两个概念。栈内存和堆内存。本次主要是讲述的是Golang的内存逃逸,故而关于内存分配和垃圾回收就不做赘述了。后面小编会单独出两篇来写这个,有需要的同学可以关注小编。关于这一块,我们现在只需要了解三点。
有了前面的基础知识,那我们简单粗暴地介绍一下内存逃逸。一个对象本应该分配在栈上面,结果分配在了堆上面,这就是内存逃逸。如下 内存逃逸的场景有哪些要了解内存逃逸的场景,首先我们要学会怎么分析内存逃逸。其实分析起来很简单,只需要一条简单的命令,即gcflags。这个是有很多参数的,此处只举一个最基本的例子。 go build -gcflags '-m' main.go 接下来我们就来讨论一下内存逃逸的场景有哪些。常见的场景有四种,小编总结为:局部指针返回,栈空间不足,动态类型,闭包引用。 局部指针返回 当我们在某个方法内定义了一个局部指针,并且将这个指针作为返回值返回时,此时就发生了逃逸。这种类型的逃逸是比较常见的,如下。
栈空间不足 众所周知,在系统中栈空间相比与总的内存来说是非常小的。如下,小编的Mac是16G*512G的,可是整个系统中栈空间大小也才8M。 而在我们的实际编码过程中,大部分Goroutine的占用空间不到10KB(这也是Golang能支持高并发的原因之一)。而其中分配给栈的更是少之又少。所以一旦某个对象体积过大时候就会发生逃逸,从栈上面转到堆上面。 如下,有两个map,space1和space2,space1长度大小都是100,space2长度大小都是10000,结果space2发生了逃逸,space1没有。 package mainimport ( 'fmt')func main() { space() fmt.Println('更多免费资料,关注公众号:不穿格子衫的程序猿')}// 栈空间溢出func space() { // 不溢出 space1 := make([]int, 100, 100) for i := 0; i < len(space1); i++ { space1[i] = i } // 溢出 space2 := make([]int, 10000, 10000) for i := 0; i < len(space2); i++ { space2[i] = i }} 动态类型 小编认为,这种内存逃逸应该是最多的,最常见的,而且还无法避免。简单地说就是被调用函数的入参是interface或者是不定参数,此时就会发生内存逃逸。如下:
哈哈哈,同学们是不是大跌眼镜,一个简简单单的Println居然也会发生内存逃逸。那么问题来了,这个是怎么导致的呢,废话不多说,直接拔掉底裤撸源码。此处就是所谓的动态类型。 闭包调用 首先说一下,这种场景是非常少的,一般没有人写这种可读性这么差的代码,小编这串代码都是参考别人的。所以小编认为,这种场景,我们只需要知道即可,大概率是碰不上的。 package mainimport ( 'fmt')func main() { fmt.Println(closure())}// 闭包逃逸func closure() func() string { return func() string { return '更多免费资料,关注公众号:不穿格子衫的程序猿' }} 分析内存逃逸的意义前面给大家列举了四种内存逃逸的场景,那么问题来了,分析内存逃逸有什么用呢?简单的总结就是两点:减轻GC的压力,提高分配速度。 上文已经说过,Golang的GC主要是针对堆的,而不是栈。试想一下,如果大量的对象从栈逃逸到堆上,是不是就会增加GC的压力。在GC的过程中会占用比较大的系统开销(一般可达到CPU容量的25%)。而且目前所有的GC都有STW这个死结,而STW会造成用户直观的'卡顿'。非常影响用户体验。 此外,堆和栈相比,堆适合不可预知大小的内存分配。但是为此付出的代价是分配速度较慢,而且会形成内存碎片。栈内存分配则会非常快。栈分配内存只需要两个CPU指令:“PUSH”和“RELEASE”,分配和释放;而堆内存分配首先需要去找到一块大小合适的内存块,之后要通过垃圾回收才能释放。 通过逃逸分析,可以尽量把那些不需要分配到堆上的变量直接分配到栈上,堆上的变量少了,会减轻分配堆内存的开销,同时也会减少GC的压力,提高程序的运行速度。 怎么避免内存逃逸最后说一下怎么避免内存逃逸吧。首先需要注意的是,Golang在编译的时候就可以确立逃逸,并不需要等到运行时。这样就给了咱们避免内存逃逸的机会。 首先咱们明确一点,小编认为没有任何方式能绝对避免内存逃逸。原因嘛,就是存在【动态类型】这种逃逸方式,几乎所有的库函数都是动态类型的。当然也不是说咱么要破罐子破摔,该避免还是要避免一下的,主要的原则有以下几种,分别针对上面几种场景。
资料环节又到了大家期待的福利时间了。本次赠送的是Golang实战案例20份。 废话不多说,各位看官大人要怎么获取呢。很简单,关注小编,私信「资料」即可获得免费获取方式。 |
|
来自: 菌心说 > 《编程+、计算机、信息技术》