Go内存管理逃逸分析
1. 前言
所谓的逃逸分析(Escape analysis)是指由编译器决定内存分配的位置吗不需要程序员指定。
函数中申请一个新的对象
- 如果分配在栈中, 则函数执行结束后可自动将内存回收
- 如果分配在堆中, 则函数执行借宿可交给GC(垃圾回收)处理
有了逃逸分析,返回函数局部变量将变得可能,除此之外,逃逸分析还跟闭包息息相关,了解哪些场景下对象会逃逸至关重要。
2. 逃逸策略
每当函数中申请新的对象,编译器会根据该对象是否被函数外部引用来决定是否逃逸:
- 如果函数外部没有引用,则优先放到栈中;
- 如果函数外部存在引用,则必定放到堆中;
注意,对于函数外部没有引用的对象,也有可能放到堆中,比如内存过大超过栈的存储能力。
3. 逃逸场景
3.1 指针逃逸
我们知道Go可以返回局部变量指针,这其实是一个典型的变量逃逸案例,示例代码如下:
package main
type Student struct {
Name string
Age int
}
func StudentRegister(name string, age int) *Student {
s := new(Student) //局部变量s逃逸到堆
s.Name = name
s.Age = age
return s
}
func main() {
StudentRegister("Jim", 18)
}
函数StudentRegister()内部s为局部变量,其值通过函数返回值返回,s本身为一指针,其指向的内存地址不会是栈而是堆,这就是典型的逃逸案例。
通过编译参数-gcflag=-m可以查看编译过程中的逃逸分析:
D:\SourceCode\GoExpert\src>go build -gcflags=-m
# _/D_/SourceCode/GoExpert/src
.\main.go:8: can inline StudentRegister
.\main.go:17: can inline main
.\main.go:18: inlining call to StudentRegister
.\main.go:8: leaking param: name
.\main.go:9: new(Student) escapes to heap
.\main.go:18: main new(Student) does not escape
可见在StudentRegister()函数中,也即代码第9行显示”escapes to heap”,代表该行内存分配发生了逃逸现象。
3.2 栈空间不足逃逸
看下面的代码,是否会产生逃逸呢?
package main
func Slice() {
s := make([]int, 1000, 1000)
for index, _ := range s {
s[index] = index
}
}
func main() {
Slice()
}
上面代码Slice()函数中分配了一个1000个长度的切片,是否逃逸取决于栈空间是否足够大。
直接查看编译提示,如下:
D:\SourceCode\GoExpert\src>go build -gcflags=-m
# _/D_/SourceCode/GoExpert/src
.\main.go:4: Slice make([]int, 1000, 1000) does not escape
我们发现此处并没有发生逃逸。那么把切片长度扩大10倍即10000会如何呢?
D:\SourceCode\GoExpert\src>go build -gcflags=-m
# _/D_/SourceCode/GoExpert/src
.\main.go:4: make([]int, 10000, 10000) escapes to heap
我们发现当切片长度扩大到10000时就会逃逸。
实际上当栈空间不足以存放当前对象时或无法判断当前切片长度时会将对象分配到堆中。
3.3 动态类型逃逸
很多函数参数为interface类型,比如fmt.Println(a …interface{}),编译期间很难确定其参数的具体类型,也会产生逃逸。
如下代码所示:
package main
import "fmt"
func main() {
s := "Escape"
fmt.Println(s)
}
上述代码s变量只是一个string类型变量,调用fmt.Println()时会产生逃逸:
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
3.4 闭包引用对象逃逸
某著名的开源框架实现了某个返回Fibonacci数列的函数:
func Fibonacci() func() int {
a, b := 0, 1
return func() int {
a, b = b, a+b
return a
}
}
该函数返回一个闭包,闭包引用了函数的局部变量a和b,使用时通过该函数获取该闭包,然后每次执行闭包都会依次输出Fibonacci数列。
完整的示例程序如下所示:
package main
import "fmt"
func Fibonacci() func() int {
a, b := 0, 1
return func() int {
a, b = b, a+b
return a
}
}
func main() {
f := Fibonacci()
for i := 0; i < 10; i++ {
fmt.Printf("Fibonacci: %d\n", f())
}
}
上述代码通过Fibonacci()获取一个闭包,每次执行闭包就会打印一个Fibonacci数值。输出如下所示:
D:\SourceCode\GoExpert\src>src.exe
Fibonacci: 1
Fibonacci: 1
Fibonacci: 2
Fibonacci: 3
Fibonacci: 5
Fibonacci: 8
Fibonacci: 13
Fibonacci: 21
Fibonacci: 34
Fibonacci: 55
Fibonacci()函数中原本属于局部变量的a和b由于闭包的引用,不得不将二者放到堆上,以致产生逃逸:
D:\SourceCode\GoExpert\src>go build -gcflags=-m
# _/D_/SourceCode/GoExpert/src
.\main.go:7: can inline Fibonacci.func1
.\main.go:7: func literal escapes to heap
.\main.go:7: func literal escapes to heap
.\main.go:8: &a escapes to heap
.\main.go:6: moved to heap: a
.\main.go:8: &b escapes to heap
.\main.go:6: moved to heap: b
.\main.go:17: f() escapes to heap
.\main.go:17: main ... argument does not escape
4 逃逸总结
- 栈上分配内存比在堆中分配内存有更高的效率
- 栈上分配的内存不需要GC处理
- 堆上分配的内存使用完毕会交给GC处理
- 逃逸分析目的是决定内分配地址是栈还是堆
- 逃逸分析在编译阶段完成
5. 注意事项
思考一下这个问题:函数传递指针真的比传值效率高吗?
我们知道传递指针可以减少底层值的拷贝,可以提高效率,但是如果拷贝的数据量小,由于指针传递会产生逃逸,可能会使用堆,也可能会增加GC的负担,所以传递指针不一定是高效的。
Go内存管理逃逸分析的更多相关文章
- Keil C动态内存管理机制分析及改进(转)
源:Keil C动态内存管理机制分析及改进 Keil C是常用的嵌入式系统编程工具,它通过init_mempool.mallloe.free等函数,提供了动态存储管理等功能.本文通过对init_mem ...
- Memcached内存管理模型分析
Memcached 是一个高性能的分布式内存对象缓存系统,它通过在内存中缓存数据和对象来减少读取数据库的次数,从而减轻RDBMS的负担,提高服务的速度.提升可扩展性.本文将基于memcached1.4 ...
- Linux内核内存管理子系统分析【转】
本文转载自:http://blog.csdn.net/coding__madman/article/details/51298718 版权声明:本文为博主原创文章,未经博主允许不得转载. 还是那张熟悉 ...
- iOS中引用计数内存管理机制分析
在 iOS 中引用计数是内存的管理方式,虽然在 iOS5 版本中,已经支持了自动引用计数管理模式,但理解它的运行方式有助于我们了解程序的运行原理,有助于 debug 程序. 操作系统的内存管理分成堆和 ...
- memcached内存管理机制分析
memached是高性能分布式内存对象系统,通过在内存中存储数据对象来减少对磁盘的数据读取次数,提高服务速度. 从业务需求出发.我们通过一条命令(如set)将一条键值对(key,value)插入mem ...
- Keil C动态内存管理机制分析及改进
Keil C是常用的嵌入式系统编程工具,它通过init_mempool.mallloe.free等函数,提供了动态存储管理等功能.本文通过对init_mempool.mallloe和free这3个Ke ...
- 深入C#内存管理来分析值类型&引用类型,装箱&拆箱,堆栈几个概念组合之间的区别
C#初学者经常被问的几道辨析题,值类型与引用类型,装箱与拆箱,堆栈,这几个概念组合之间区别,看完此篇应该可以解惑. 俗话说,用思想编程的是文艺程序猿,用经验编程的是普通程序猿,用复制粘贴编程的是2B程 ...
- redis 源代码分析(一) 内存管理
一,redis内存管理介绍 redis是一个基于内存的key-value的数据库,其内存管理是很重要的,为了屏蔽不同平台之间的差异,以及统计内存占用量等,redis对内存分配函数进行了一层封装,程序中 ...
- JVM之基础概念(运行时数据区域、TLAB、逃逸分析、分层编译)
运行时数据区域 JDK8 之前的内存布局 JDK8 之后的 JVM 内存布局 JDK8 之前,Hotspot 中方法区的实现是永久代(Perm),JDK8 开始使用元空间(Metaspace),以前永 ...
- Cocos2d-x 3.1 内存管理机制
Cocos2d-x使用的内存管理方式是引用计数.引用计数是一种非常有效的机制.通过给每个对象维护一个引用计数器,记录该对象当前被引用的次数.当对象添加一次引用时,计数器加1:而对象失去一次引用时.计数 ...
随机推荐
- ADOConnection调用连接窗口
uses AdoConEd; 使用函数 1.EditConnectionString(ADOConnection); 2.PromptDataSource
- gitee提交过程
https://gitee.com/ 一个线上代码云端软件开发协作平台 首先注册一个账号 然后添加新的仓库 仓库名称和路径是必填项 然后创建项目 选择 克隆存储数据库 存储库位置是网站获取的git位 ...
- JavaScript基础知识整理(引用类型-Object)
Object类型 其他的引用类型都是Object类型的实例,创建Object实例有两种方式 (1)使用构造函数 var obj = new Object(); obj.name = "xia ...
- 杭电oj--1019题C++实现
这道题有两个问题: 首先,是求利用数论的辗转相除法求最大公约数,后再求最小公倍数m*n/gcd(m,n),其中,m*n可能会超过int 数据范围,所以,该语句换成m/gcd(m,n)*n. 然后是如果 ...
- 48. Rotate Image via java
need to use scratch to find the pattern class Solution { public void rotate(int[][] matrix) { int n= ...
- Pnetlab中锐捷镜像反复重启或telnet无法键入内容
PNETLab 版本: 5.2.7 或 5.3.3等 锐捷镜像版本: V1.03 故障详情: 基于前文的系统环境和锐捷镜像.替换后的yml文件,更新PnetLAB版本到5.3.3后,设备循环重启,无法 ...
- Hackintool查看CFG锁显示空白
Hackintool是黑苹果配置的得力工具,通过在Hackintool > 工具 > 从AppleIntelInfo中获取 可以看到cfg是否成功解锁.但是如果点击该按钮后输入密码执行CP ...
- AIO基本编写
一. public class Server { //线程池 private ExecutorService executorService; //线程组 private AsynchronousCh ...
- CDH命令
1.检查http服务是否开启 systemctl status httpd.service 本次没有开启是因为删了Log日志 通过查看http状态发现里面有个引用问题 重新建一个空的Log文件夹重启服 ...
- vue项目中 vscode 保存时自动格式化设置,保持单引号和去除多余分号、逗号
1.settings.json中添加: "prettier.semi": false, // 取消自动加分号 "prettier.singleQuote": t ...