1. 前言

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

函数中申请一个新的对象

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

有了逃逸分析,返回函数局部变量将变得可能,除此之外,逃逸分析还跟闭包息息相关,了解哪些场景下对象会逃逸至关重要。

2. 逃逸策略

每当函数中申请新的对象,编译器会根据该对象是否被函数外部引用来决定是否逃逸:

  1. 如果函数外部没有引用,则优先放到栈中;
  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逃逸分析的更多相关文章

  1. JVM中启用逃逸分析

    -XX:+DoEscapeAnalysis 逃逸分析优化JVM原理我们知道java对象是在堆里分配的,在调用栈中,只保存了对象的指针.当对象不再使用后,需要依靠GC来遍历引用树并回收内存,如果对象数量 ...

  2. JVM笔记-逃逸分析

    参考: http://www.iteye.com/topic/473355http://blog.sina.com.cn/s/blog_4b6047bc01000avq.html 什么是逃逸分析(Es ...

  3. Go变量逃逸分析

    目录 什么是逃逸分析 为什么要逃逸分析 逃逸分析是怎么完成的 逃逸分析实例 总结 写过C/C++的同学都知道,调用著名的malloc和new函数可以在堆上分配一块内存,这块内存的使用和销毁的责任都在程 ...

  4. JVM逃逸分析

    开启逃逸分析: -server -XX:+DoEscapeAnalysis -XX:+PrintGCDetail -Xmx10m -Xms10m 关闭逃逸分析: -server -XX:-DoEsca ...

  5. 逃逸分析(Escape Analysis)

    一.什么是逃逸 逃逸是指在某个方法之内创建的对象,除了在方法体之内被引用之外,还在方法体之外被其它变量引用到:这样带来的后果是在该方法执行完毕之后,该方法中创建的对象将无法被GC回收,由于其被其它变量 ...

  6. golang逃逸分析和竞争检测

    最近在线上发现一块代码逻辑在执行N次耗时波动很大1ms~800ms,最开始以为是gc的问题,对代码进行逃逸分析,看哪些变量被分配到堆上了,后来发现是并发编程时对一个切片并发的写,导致存在竞争,类似下面 ...

  7. 深入理解Java中的逃逸分析

    在Java的编译体系中,一个Java的源代码文件变成计算机可执行的机器指令的过程中,需要经过两段编译,第一段是把.java文件转换成.class文件.第二段编译是把.class转换成机器指令的过程. ...

  8. Go 语言机制之逃逸分析

    https://blog.csdn.net/weixin_38975685/article/details/79788254   Go 语言机制之逃逸分析 https://blog.csdn.net/ ...

  9. JVM的逃逸分析

    我们都知道Java中的对象默认都是分配到堆上,在调用栈中,只保存了对象的指针.当对象不再使用后,需要依靠GC来遍历引用树并回收内存.如果堆中对象数量太多,回收对象还有整理内存,都会会带来时间上的消耗, ...

  10. Java之JVM逃逸分析

    引言: 逃逸分析(Escape Analysis)是众多JVM技术中的一个使用不多的技术点,本文将通过一个实例来分析其使用场景. 概念 逃逸分析,是一种可以有效减少Java 程序中同步负载和内存堆分配 ...

随机推荐

  1. 微信小程序云开发-数据库-获取用户添加的数据到数据库

    一.列表页面新增[添加商品]按钮 在列表页增加[添加商品]按钮,按钮绑定事件toAdd(),用户点击该按钮跳转到添加商品页面. 在js文件中写toAdd()函数,作用是点击[添加商品]按钮,跳转到[添 ...

  2. SQL修改列名,增加列,删除列语句的写法

    1.修改数据表名 ALTER TABLE [表名.]OLD_TABLE_NAME RENAME TO NEW_TABLE_NAME; 2.修改列名 ALTER TABLE [表名.]TABLE_NAM ...

  3. Redis.conf分析

    Redis.conf 单位 配置文件对大小写不敏感 # 1k => 1000 bytes # 1kb => 1024 bytes # 1m => 1000000 bytes # 1m ...

  4. NOIP&CSP PJ 难度刷题记录

    前言 本来不想写前言的(>人<:) 这只是 mjl 给我们布置的作业,并不是我自己在刷题! 不保证所有代码的正确性,它们仅仅是通过了所有数据点而已. 1.模拟板块 整体难度:红~黄(模拟不 ...

  5. C函数调用(2)

    1 //函数调用 2 3 #include <stdio.h> 4 #include <stdlib.h> 5 #include <math.h> 6 //根据传入 ...

  6. 大厂需要什么样的 Android 开发?

    前言 昨天和一个百度的朋友闲聊,他说根据最近招聘 Android工程师的经验来看,大部分候选人在工作 3 年的时候基本都会遇上一道难过的坎. 为啥这么说呢? 因为工作一段时间之后,大部分工程师都已经完 ...

  7. 程序员被老板要求两个月做个APP,要不比京东差,网友:做一个快捷方式,直接链到京东

    隔行如隔山,这句话说得一点都没错.做一个程序员,很多人都会羡慕,也有很多人会望而却步. 作为一个外行人,你别看程序员每天坐在电脑前敲敲键盘打打代码,以为很简单,其实啊也只有程序员自己明白,任何一个看似 ...

  8. Spring源码阅读-BeanFactory体系结构分析

    BeanFactory是Spring中非常重要的一个类,搞懂了它,你就知道了bean的初始化和摧毁过程,对于深入理解IOC有很大的帮助. BeanFactory体系结构 首先看一下使用IDEA生成的继 ...

  9. 尝新体验ASP.NET Core 6预览版本中发布的最小Web API(minimal APIS)新特性

    本文首发于<尝新体验ASP.NET Core 6预览版本中发布的最小Web API(minimal APIS)新特性> 概述 .NET开发者们大家好,我是Rector. 几天前(美国时间2 ...

  10. Java多线程间的数据共享

    下面的程序演示了一个对象被两个线程访问的方法,"monitor.gotMessage();"这一句虽然是monitor对象的方法,但却是运行在"MyObject" ...