逃逸分析

内存逃逸:栈上的内存逃逸到了堆上的现象就称为内存逃逸

概念

程序在编译阶段根据代码来确认哪些变量分配在栈区,哪些变量分配在堆区。这样可以防止过多内存在堆上分配,减轻GC压力以及程序STW的时间

原理

  • 指向栈对象的指针不能存储在堆中

  • 指向栈对象的指针不能超过该对象的存活期,也就是指针不能再栈对象被销毁后依然存活

如何查看

通过如下方式编译代码可以看到逃逸分析

go build -gcflags='-m -m -l'
  • -m -m 能看到所有编译器优化
  • -l 禁用掉内联优化

分析例子

函数返回局部变量指针

package maintype st struct {a intb int
}func Add(x, y int) *st {r := st{a: x, b: y}return &r
}func main() {res := Add(3, 5)res.a = 10
}

查看逃逸分析结果

$ go build -gcflags="-m -m -l" test.go
# command-line-arguments
./test.go:9:2: r escapes to heap:
./test.go:9:2:   flow: ~r2 = &r:
./test.go:9:2:     from &r (address-of) at ./test.go:10:9
./test.go:9:2:     from return &r (return) at ./test.go:10:2
./test.go:9:2: moved to heap: r
note: module requires Go 1.17

可以看到局部变量指针r发生了内存逃逸

函数返回局部变量

package maintype st struct {a intb int
}func Add(x, y int) st {r := st{a: x, b: y}return r
}func main() {res := Add(3, 5)res.a = 10
}

逃逸分析

$ go build -gcflags="-m -m -l" test.go
$
  • 没有任何信息,说明没有发生逃逸

interface类型逃逸

package mainimport "fmt"func main() {res := 10fmt.Print(res)
}

逃逸分析

$ go build -gcflags="-m -m -l" test.go
# command-line-arguments
./test.go:7:11: res escapes to heap:
./test.go:7:11:   flow: {storage for ... argument} = &{storage for res}:
./test.go:7:11:     from res (spill) at ./test.go:7:11
./test.go:7:11:     from ... argument (slice-literal-element) at ./test.go:7:11
./test.go:7:11:   flow: {heap} = {storage for ... argument}:
./test.go:7:11:     from ... argument (spill) at ./test.go:7:11
./test.go:7:11:     from fmt.Print(... argument...) (call parameter) at ./test.go:7:11
./test.go:7:11: ... argument does not escape
./test.go:7:11: res escapes to heap
note: module requires Go 1.17

可以看到res发生了逃逸,这是因为:

  • 编译器很难确定interface{}的具体类型,所以会发生逃逸

但是我们可以看到res本身没有move to heap,这是因为编译器只是把res存储的值存储到了堆区,res本身依然还是在栈区,但是我们可以做以下修改:

package mainimport "fmt"func main() {res := 10fmt.Print(&res)
}

逃逸分析

$ go build -gcflags="-m -m -l" test.go
# command-line-arguments
./test.go:6:2: res escapes to heap:
./test.go:6:2:   flow: {storage for ... argument} = &res:
./test.go:6:2:     from &res (address-of) at ./test.go:7:12
./test.go:6:2:     from &res (interface-converted) at ./test.go:7:12
./test.go:6:2:     from ... argument (slice-literal-element) at ./test.go:7:11
./test.go:6:2:   flow: {heap} = {storage for ... argument}:
./test.go:6:2:     from ... argument (spill) at ./test.go:7:11
./test.go:6:2:     from fmt.Print(... argument...) (call parameter) at ./test.go:7:11
./test.go:6:2: moved to heap: res
./test.go:7:11: ... argument does not escape
note: module requires Go 1.17

会发现,res本身也逃逸到了堆区,这是因为fmt.Print输出的是res的地址,因此编译器会将地址的值存储到了堆区,但是堆上的对象不能存储一个栈上的地址,因为栈变量销毁后,其地址就无效了,那堆区中存储的地址也会无效,因此需要将res也逃逸到堆区。

闭包产生的逃逸

package mainfunc add() func() int {num := 0f := func() int {num++return num}return f
}func main() {add()
}

逃逸分析:

$ go build -gcflags="-m -m -l" test.go
# command-line-arguments
./test.go:6:3: add.func1 capturing by ref: num (addr=true assign=true width=8)
./test.go:5:7: func literal escapes to heap:
./test.go:5:7:   flow: f = &{storage for func literal}:
./test.go:5:7:     from func literal (spill) at ./test.go:5:7
./test.go:5:7:     from f := func literal (assign) at ./test.go:5:4
./test.go:5:7:   flow: ~r0 = f:
./test.go:5:7:     from return f (return) at ./test.go:9:2
./test.go:4:2: num escapes to heap:
./test.go:4:2:   flow: {storage for func literal} = &num:
./test.go:4:2:     from func literal (captured by a closure) at ./test.go:5:7
./test.go:4:2:     from num (reference) at ./test.go:6:3
./test.go:4:2: moved to heap: num
./test.go:5:7: func literal escapes to heap
note: module requires Go 1.17

可以看到f和闭包中的num都发生了内存逃逸。因为函数也是一个指针类型,因此当做返回值时也发生了逃逸。而只要调用了f,就会调用num,因此num也需要逃逸到堆区

变量大小不确定或者栈空间不足

  • 当栈空间足够时,不会发生逃逸,但是当变量过大时,已经完全超过栈空间的大小时,将会发生逃逸到堆上分配内存
  • 初始化切片时,没有直接指定大小,而是填入的变量,这种情况为了保证内存的安全,编译器也会触发逃逸,在堆上进行分配内存

向channel发送指针数据

package mainfunc main() {ch1 := make(chan *int, 1)y := 5py := &ych1 <- py
}

逃逸分析

$ go build -gcflags="-m -m -l" test.go
# command-line-arguments
./test.go:5:2: y escapes to heap:
./test.go:5:2:   flow: py = &y:
./test.go:5:2:     from &y (address-of) at ./test.go:6:8
./test.go:5:2:     from py := &y (assign) at ./test.go:6:5
./test.go:5:2:   flow: {heap} = py:
./test.go:5:2:     from ch1 <- py (send) at ./test.go:7:6
./test.go:5:2: moved to heap: y
note: module requires Go 1.17

编译器无法知道channel 中的数据会被哪个 goroutine 接收,无法知道什么时候释放

总结

  • 逃逸分析在编译阶段确定哪些变量可以分配在栈上,哪些变量分配在堆上
  • 减轻了GC压力,提供程序的运行速度
  • 栈上内存使用完毕不需要GC处理,堆上内存使用完毕会交给GC处理
  • 尽量减少逃逸代码,减轻GC压力

Golang内存逃逸相关推荐

  1. Golang的GC和内存逃逸

    简介 每个版本的Golang的垃圾回收都在不断优化中,而且方法和策略都在变化,因此这里只是总结出以下几个关键点: 什么样的数据需要GC 触发GC的条件是什么 GC时发生了什么 能否从代码层面上提高GC ...

  2. golang byte转string_golang面试题:怎么避免内存逃逸?

    问题 怎么避免内存逃逸? 怎么答 在runtime/stubs.go:133有个函数叫noescape.noescape可以在逃逸分析中隐藏一个指针.让这个指针在逃逸分析中不会被检测为逃逸. // n ...

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

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

  4. GoLang的逃逸分析

    GoLang的垃圾回收机制可以进行自动内存管理让我们的代码更简洁,同时发生内存泄漏的可能性更小.然而,GC会定期停止并收集未使用的对象,因此还是会增加程序的开销.Go的编译器十分聪明,比如决定变量需要 ...

  5. c++对象回收问题_从垃圾回收解开Golang内存管理的面纱之三垃圾回收

    四.垃圾回收 终于说到垃圾回收了,我的初衷就是要搞明白垃圾回收的算法,谁知道衍生出来那么多东西,哈哈. 5.1 常见垃圾回收策略 所谓垃圾回收,即为释放我们不再使用的对象的内存,话不多说,我们一一分析 ...

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

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

  7. GO语言内存逃逸图文分析

    在分析内存逃逸之前我们需要知道我们的内存空间的分布图,为内存逃逸的分析做一些铺垫,这是内存分布,当系统知道变量什么时候的生命周期和内存分配大小的时候我们都将其分配在栈区 (必知)分析内存逃逸之前我们需 ...

  8. golang内存分配概述

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

  9. 分析go程序内存逃逸情况

    分析go程序内存逃逸情况 go build -gcflags -m app/admin/service/cmd/server/main.go app/admin/service/cmd/server/ ...

  10. golang 内存管理

    文章目录 内存管理 内存分配器 线性分配(Bump Allocator) 空闲链表分配(Free-List Allocator) 线程缓存分配 (Thread-Caching Malloc,TCMal ...

最新文章

  1. MFC CListCtrl 取消选中
  2. Atitit. 高级软件工程师and 普通的区别 高级编程的门槛总结
  3. appium 环境搭建 java
  4. 深入探究.Net Core Configuration读取配置的优先级
  5. LeetCode 1891. 割绳子(二分查找)
  6. 实现灵活的IT架构的三个要点
  7. TreeViewVisitor: 一个快捷访问 TreeView 控件节点的帮助类
  8. Android学习笔记——ProgressBarHandler
  9. 基于经纬度做航线图可视化
  10. 矩阵卷积运算的三种方式及C语言实现
  11. 概率论 方差公式_数学提高方差的计算公式是什么
  12. VR/AR眼镜Type-C转接器边投屏边PD快充方案
  13. 将图形中线条或者图案坐标点进行提取
  14. JFS 文件系统概述及布局分析
  15. 初始Vue响应式原理~~
  16. c/c++ 计算屏幕的PPI
  17. 87个C#帮助类,各种功能性代码(转载自微信公众号:dotNET全栈开发)
  18. “她经济”作祟医美,美呗如何变美?
  19. 输出《易经》六十四卦的卦象符号
  20. 精准定位Ubuntu网速最快的软件更新网站

热门文章

  1. 挂yy协议的服务器,yy协议挂机软件
  2. Winfrom 桌面弹窗拦截 关闭进程简易程序 源代码下载
  3. selenium:表单frame切换和句柄窗口切换
  4. 软件测试——课程感想
  5. 计算机网络语音传输杂音回音,QQ语音时,怎么消除麦克风回音、噪音、杂音
  6. 小满 前端埋点SDK 带你 从0 开发 并且发布npm
  7. ''' 疯狂填词 创建一个疯狂填词(Mad Libs)程序,它将读入文本文件
  8. unity服务器无响应怎么办,Windbg调试Unity3d 卡死 无响应等问题测试
  9. day2-requests和bs4
  10. linux malloc内存申请相关参数设置