. golang 的内存分配逃逸 于堆和栈

注意我们此处谈到的堆和栈是对操作系统中的,这个和数据结构中的堆和栈还是又一定区别的。

1. 关于 堆和栈

栈 可以简单得理解成一次函数调用内部申请到的内存,它们会随着函数的返回把内存还给系统。

func F() {        temp := make([]int, 0, 20)        ...
}

类似于上面代码里面的temp变量,只是内函数内部申请的临时变量,并不会作为返回值返回,它就是被编译器申请到栈里面。

申请到 栈内存 好处:函数返回直接释放,不会引起垃圾回收,对性能没有影响。

再来看看堆得情况之一如下代码:

func F() []int{        a := make([]int, 0, 20)        return a
}

而上面这段代码,申请的代码一模一样,但是申请后作为返回值返回了,编译器会认为变量之后还会被使用,当函数返回之后并不会将其内存归还,那么它就会被申请到 堆 上面了。

申请到堆上面的内存才会引起垃圾回收,如果这个过程(特指垃圾回收不断被触发)过于高频就会导致 gc 压力过大,程序性能出问题。

我们再看看如下几个例子:

func F() {     a := make([]int, 0, 20)     // 栈 空间小       b := make([]int, 0, 20000) // 堆 空间过大        l := 20        c := make([]int, 0, l) // 堆 动态分配不定空间
}

像是 b 这种 即使是临时变量,申请过大也会在堆上面申请。

对于 c 编译器对于这种不定长度的申请方式,也会在堆上面申请,即使申请的长度很短。

2. 逃逸分析(Escape analysis

所谓逃逸分析(Escape analysis)是指由编译器决定内存分配的位置,不需要程序员指定。

在函数中申请一个新的对象:

  • 如果分配 在栈中,则函数执行结束可自动将内存回收;
  • 如果分配在堆中,则函数执行结束可交给GC(垃圾回收)处理;

注意,对于函数外部没有引用的对象,也有可能放到堆中,比如内存过大超过栈的存储能力。

3. 逃逸场景(什么情况才分配到堆中)

3.1 指针逃逸

Go可以返回局部变量指针,这其实是一个典型的变量逃逸案例,示例代码如下:

package main
type Student struct {Name stringAge  int
}func StudentRegister(name string, age int) *Student {s := new(Student) //局部变量s逃逸到堆s.Name = names.Age = agereturn s
}func main() {StudentRegister("Jim", 18)
}

虽然 在函数 StudentRegister() 内部 s 为局部变量,其值通过函数返回值返回,s 本身为一指针,其指向的内存地址不会是栈而是堆,这就是典型的逃逸案例。

终端运行命令查看逃逸分析日志:

go build -gcflags=-m

可见在StudentRegister()函数中,也即代码第9行显示”escapes to heap”,代表该行内存分配发生了逃逸现象。

3.2 栈空间不足逃逸(空间开辟过大)

package mainfunc Slice() {s := make([]int, 1000, 1000)for index, _ := range s {s[index] = index}
}func main() {Slice()
}

上面代码Slice()函数中分配了一个1000个长度的切片,是否逃逸取决于栈空间是否足够大。 直接查看编译提示,如下:

所以只是1000的长度还不足以发生逃逸现象。然后就x10倍吧

package mainfunc Slice() {s := make([]int, 10000, 10000)for index, _ := range s {s[index] = index}
}func main() {Slice()
}

分析如下:

当切片长度扩大到10000时就会逃逸。

实际上当栈空间不足以存放当前对象时或无法判断当前切片长度时会将对象分配到堆中。

3.3 动态类型逃逸(不确定长度大小)

很多函数参数为interface类型,比如fmt.Println(a …interface{}),编译期间很难确定其参数的具体类型,也能产生逃逸。

如下代码所示:

package mainimport "fmt"func main() {s := "Escape"fmt.Println(s)
}

逃逸分下如下:

D:\SourceCode\GoExpert\src>go build -gcflags=-m

# _/D_/SourceCode/GoExpert/src

.\main.go:7: s escapes to heap

.\main.go:7: main ... argument does not escape

又或者像前面提到的例子:

func F() {a := make([]int, 0, 20)     // 栈 空间小b := make([]int, 0, 20000) // 堆 空间过大 逃逸l := 20c := make([]int, 0, l) // 堆 动态分配不定空间 逃逸
}

3.4 闭包引用对象逃逸

Fibonacci数列的函数:

package mainimport "fmt"func Fibonacci() func() int {a, b := 0, 1return func() int {a, b = b, a+breturn a}
}func main() {f := Fibonacci()for i := 0; i < 10; i++ {fmt.Printf("Fibonacci: %d\n", f())}
}

输出如下:

~/go/src/gitHub/test/pool  go run main.go

Fibonacci: 1

Fibonacci: 1

Fibonacci: 2

Fibonacci: 3

Fibonacci: 5

Fibonacci: 8

Fibonacci: 13

Fibonacci: 21

Fibonacci: 34

Fibonacci: 55

逃逸如下:

~/go/src/gitHub/test/pool  go build -gcflags=-m

# gitHub/test/pool

./main.go:7:9: can inline Fibonacci.func1

./main.go:7:9: func literal escapes to heap

./main.go:7:9: func literal escapes to heap

./main.go:8:10: &b escapes to heap

./main.go:6:5: moved to heap: b

./main.go:8:13: &a escapes to heap

./main.go:6:2: moved to heap: a

./main.go:17:34: f() escapes to heap

./main.go:17:13: main ... argument does not escape

Fibonacci()函数中原本属于局部变量的a和b由于闭包的引用,不得不将二者放到堆上,以致产生逃逸。

3.5 作用域内变量逃逸

对作用域内的变量取地址,作用域外仍可以解引用指向此变量。

package mainimport "fmt"func isMemoryOut(a int)(*int,bool){value:=areturn &value,true
}func main() {var ret *intif value,ok:=isMemoryOut(1);ok{ret=value}fmt.Println(*ret)
}

# memoryAnylized
.\memoryAnylized.go:5:6: can inline isMemoryOut
.\memoryAnylized.go:12:26: inlining call to isMemoryOut
.\memoryAnylized.go:15:13: inlining call to fmt.Println
.\memoryAnylized.go:7:9: &value escapes to heap
.\memoryAnylized.go:6:2: moved to heap: value
.\memoryAnylized.go:15:14: *ret escapes to heap
.\memoryAnylized.go:15:13: io.Writer(os.Stdout) escapes to heap
.\memoryAnylized.go:12:26: main &value does not escape
.\memoryAnylized.go:15:13: main []interface {} literal does not escape
<autogenerated>:1: os.(*File).close .this does not escape
<autogenerated>:1: os.(*File).isdir .this does not escape

逃逸分析的作用是什么呢?

  1. 逃逸分析的好处是为了减少gc的压力,不逃逸的对象分配在栈上,当函数返回时就回收了资源,不需要gc标记清除。
  2. 逃逸分析完后可以确定哪些变量可以分配在栈上,栈的分配比堆快,性能好(逃逸的局部变量会在堆上分配 ,而没有发生逃逸的则有编译器在栈上分配)。
  3. 同步消除,如果你定义的对象的方法上有同步锁,但在运行时,却只有一个线程在访问,此时逃逸分析后的机器码,会去掉同步锁运行。

逃逸总结:

  • 栈上分配内存比在堆中分配内存有更高的效率
  • 栈上分配的内存不需要GC处理
  • 堆上分配的内存使用完毕会交给GC处理
  • 逃逸分析目的是决定内分配地址是栈还是堆
  • 逃逸分析在编译阶段完成

提问:函数传递指针真的比传值效率高吗?

我们知道传递指针可以减少底层值的拷贝,可以提高效率,但是如果拷贝的数据量小,由于指针传递会产生逃逸,可能会使用堆,也可能会增加GC的负担,所以传递指针不一定是高效的。

在官网 (golang.org) FAQ 上有一个关于变量分配的问题如下:

From a correctness standpoint, you don’t need to know. Each variable in Go exists as long as there are references to it. The storage location chosen by the implementation is irrelevant to the semantics of the language.

The storage location does have an effect on writing efficient programs. When possible, the Go compilers will allocate variables that are local to a function in that function’s stack frame.

However, if the compiler cannot prove that the variable is not referenced after the function returns, then the compiler must allocate the variable on the garbage-collected heap to avoid dangling pointer errors. Also, if a local variable is very large, it might make more sense to store it on the heap rather than the stack.

In the current compilers, if a variable has its address taken, that variable is a candidate for allocation on the heap. However, a basic escape analysis recognizes some cases when such variables will not live past the return from the function and can reside on the stack.

翻译如下:

如何得知变量是分配在栈(stack)上还是堆(heap)上?

准确地说,你并不需要知道。Golang 中的变量只要被引用就一直会存活,存储在堆上还是栈上由内部实现决定而和具体的语法没有关系。

知道变量的存储位置确实和效率编程有关系。如果可能,Golang 编译器会将函数的局部变量分配到函数栈帧(stack frame)上。 然而,如果编译器不能确保变量在函数 return之后不再被引用,编译器就会将变量分配到堆上。而且,如果一个局部变量非常大,那么它也应该被分配到堆上而不是栈上。

当前情况下,如果一个变量被取地址,那么它就有可能被分配到堆上。然而,还要对这些变量做逃逸分析,如果函数return之后,变量不再被引用,则将其分配到栈上。


. golang 临时对象池sync.Pool

1. 内存碎片化问题:

实际项目基本都是通过

c := make([]int, 0, l)

来申请内存,长度都是不确定的,自然而然这些变量都会申请到堆上面了。

Golang使用的垃圾回收算法是『标记——清除』。

简单得说,就是程序要从操作系统申请一块比较大的内存,内存分成小块,通过链表链接。

每次程序申请内存,就从链表上面遍历每一小块,找到符合的就返回其地址,没有合适的就从操作系统再申请。如果申请内存次数较多,而且申请的大小不固定,就会引起内存碎片化的问题。

申请的堆内存并没有用完,但是用户申请的内存的时候却没有合适的空间提供。这样会遍历整个链表,还会继续向操作系统申请内存。这就能解释我一开始描述的问题,申请一块内存变成了慢语句。

Golang内存分配逃逸分析相关推荐

  1. golang内存分配概述

    golang内存分配概述 golang的内存分配机制主要类似于tcmalloc机制,来快速高效的分配与管理内存,从而高效分配与管理内存. 有关 tcmalloc 的详细资料大家可参考官网,简单概括就是 ...

  2. Golang 内存分配与逃逸分析

    参考:灵魂拷问:Go 语言这个变量到底分配到哪里了? 来源于 公众号: 脑子进煎鱼了 ,作者陈煎鱼. 我们在写代码的时候,有时候会想这个变量到底分配到哪里了?这时候可能会有人说,在栈上,在堆上.信我准 ...

  3. go-内存管理篇(二) 万字总结-golang内存分配篇

    前言 本文是讲解Golang内存管理的第二篇,在第一篇中我们提到,Golang的内存分配模式与TCMalloc是极其相似的. 所以先来回顾一下TCMalloc相关知识点. Page:TCMalloc也 ...

  4. 【 C 】动态内存分配案例分析

    声明一个指向char类型的指针,可以在声明的时候就对其进行初始化,这样是合理的. 例如: E1: #include <stdio.h> #include <stdlib.h> ...

  5. 从内存分配角度分析c和java里的static 关键字.

    即使作为Java的初学者, 对this 和 static 这两个关键字都不会陌生. 其实也不难理解: this 关键字:  指的是对象的本身(注意不是类本身)  跟.net 语言的Me 关键字类似. ...

  6. golang 内存分配

    内存布局结构图 从上述结构图来看,内存分配器还是有一点小复杂的,但根据具体的逻辑层次可以拆分成三大模块--cache,central,heap,然后一个一个的模块分析下去,逻辑就显得特别清晰明了了.位 ...

  7. 3.内存分配、逃逸分析与栈上分配、直接内存和运行时常量池、基本类型的包装类和常量池、TLAB、可达性分析算法(学习笔记)

    3.JVM内存分配 3.1.内存分配概述 3.2.内存分配–Eden区域 3.3.内存分配–大对象直接进老年代 3.3.1.背景 3.3.2.解析 3.4.内存分配–长期存活的对象进去老年代 3.5. ...

  8. golang int 转string_Golang的逃逸分析

    逃逸分析 逃逸分析(Escape Analysis)指的是将变量的内存分配在合适的地方(堆或者栈). 在函数中申请内存有2种情况: - 如果内存分配在栈(stack)上,当函数退出的时候,这部分内存会 ...

  9. 记录一次Golang逃逸分析

    今天偶然看到Golang关于内存的文章,其中涉及了一点逃逸分析,由于去年之前都是专研C++,Golang也是去年11月才开始学习的,学完就马上进入项目了,没有深究底层,准备这段时间边改论文边开始仔细学 ...

  10. JVM---堆(逃逸分析与代码优化)

    堆-逃逸分析 堆是分配对象的唯一选择么? 在<深入理解Java虚拟机>中关于Java堆内存有这样一段描述: 随着JIT编译期的发展与逃逸分析技术逐渐成熟,栈上分配.标量替换优化技术将会导致 ...

最新文章

  1. [Ruby01]Class, Module, Object,Kernel的关系
  2. 推荐 Python 十大经典练手项目,让你的 Python 技能点全亮!
  3. android 阴影背景显示文字_公众号排版如何做出“果冻”文字效果?
  4. [渝粤教育] 云南大学 高等数学B(2) 参考 资料
  5. redis常见问题和解决方案
  6. 纯前端表格控件SpreadJS——轻松搞定数据绑定
  7. 因果信号的傅里叶变换_信号傅里叶变换系列文章(1):傅里叶级数、傅里叶系数以及傅里叶变换...
  8. java 以10为底的对数_获取Java中值的以10为底的对数
  9. python读取163邮件内容_python模拟登陆163邮箱并下载邮件内容(第三版代码片段)
  10. bluetooth 驱动 Makefile
  11. 安装11.2.0.3时,OUI的log报错:OUI-10066:Not All The Dependencies For The Component ... Could Be Found
  12. IDEA连接数据库出现the server time zone value ‘�й���׼ʱ��‘ is unrecognized or represents more than one time
  13. 又一巨头告急!曾年赚500亿,如今连房租都付不起!
  14. canvas实现水印效果
  15. 自动控制原理笔记-根轨迹法
  16. VIVO可能在某一段时间内手机充电数据线上并没有ID脚
  17. Easy-UI入门案例
  18. 缺陷管理 如何发现更多的缺陷
  19. 高德地图某一城市地铁路线高亮解决方案
  20. 微信官方项目,企业微信考勤机,行动起来月入三万+

热门文章

  1. 设计师的色彩理论,你知道不同色彩的秘密吗?
  2. 广东第一高中生_前广东第一高中生!曾打爆职业球员!一米八的他还能风车暴扣!...
  3. 广东第一高中生_广东男篮签下全美第一高中生 NBA状元热门征战CBA
  4. android 手机安装windows7,手机怎么安装win7系统 安卓手机装win7系统教程
  5. cad图形不见了怎么办_画好的cad图纸文件不见了怎么找回?
  6. 什么是埋点?我们为什么需要埋点?(原作者:知乎 原志Growing)
  7. 天空之镜?瑞士冰川?Nono,这里其实是新疆!
  8. 计划三年投入十亿资金,统信UOS生态腾飞加速
  9. 一个百万富翁遇到一个陌生人,陌生人找他谈一个换钱的计划,该计划如下:我每天给你十万元,而你第一天只需给我一分钱;第二天我仍给你十万元,你给我两分钱;第三天我仍给你十万元,你给我四分钱;....,你每天
  10. 如何搭建云服务器以及使用