不做大哥好多年 不做大哥好多年
首页
  • MySQL
  • Redis
  • Elasticsearch
  • Kafka
  • Etcd
  • MongoDB
  • TiDB
  • RabbitMQ
  • 01.GO基础
  • 02.面向对象
  • 03.并发编程
  • 04.常用库
  • 05.数据库操作
  • 06.Beego框架
  • 07.Beego商城
  • 08.GIN框架
  • 09.GIN论坛
  • 10.微服务
  • 01.Python基础
  • 02.Python模块
  • 03.Django
  • 04.Flask
  • 05.SYL
  • 06.Celery
  • 10.微服务
  • 01.Java基础
  • 02.面向对象
  • 03.Java进阶
  • 04.Web基础
  • 05.Spring框架
  • 100.微服务
  • Docker
  • K8S
  • 容器原理
  • Istio
  • 数据结构
  • 算法基础
  • 算法题分类
  • 前置知识
  • PyTorch
  • 01.Python
  • 02.GO
  • 03.Java
  • 04.业务问题
  • 05.关键技术
  • 06.项目常识
  • 10.计算机基础
  • Linux基础
  • Linux高级
  • Nginx
  • KeepAlive
  • ansible
  • zabbix
  • Shell
  • Linux内核

逍遥子

不做大哥好多年
首页
  • MySQL
  • Redis
  • Elasticsearch
  • Kafka
  • Etcd
  • MongoDB
  • TiDB
  • RabbitMQ
  • 01.GO基础
  • 02.面向对象
  • 03.并发编程
  • 04.常用库
  • 05.数据库操作
  • 06.Beego框架
  • 07.Beego商城
  • 08.GIN框架
  • 09.GIN论坛
  • 10.微服务
  • 01.Python基础
  • 02.Python模块
  • 03.Django
  • 04.Flask
  • 05.SYL
  • 06.Celery
  • 10.微服务
  • 01.Java基础
  • 02.面向对象
  • 03.Java进阶
  • 04.Web基础
  • 05.Spring框架
  • 100.微服务
  • Docker
  • K8S
  • 容器原理
  • Istio
  • 数据结构
  • 算法基础
  • 算法题分类
  • 前置知识
  • PyTorch
  • 01.Python
  • 02.GO
  • 03.Java
  • 04.业务问题
  • 05.关键技术
  • 06.项目常识
  • 10.计算机基础
  • Linux基础
  • Linux高级
  • Nginx
  • KeepAlive
  • ansible
  • zabbix
  • Shell
  • Linux内核
  • Python

  • GO

    • 01.GMP模型 ✅
    • 02.Map原理 ✅
    • 03.sync_map ✅
    • 04.sync.Pool
    • 05.垃圾回收 ✅
    • 06.channel
    • 07.内存逃逸
      • 01.内存逃逸
        • 1、其他语言内存回收机制
        • 2、什么是内存逃逸
        • 3、内存分配堆和栈
        • 1)内存分片概述
        • 2)分配到栈里
        • 3)分配到堆里
        • 4)分配到栈里和堆里区别
      • 02.逃逸的几种情况
        • 0、逃逸分析
        • 1、指针逃逸
        • 2、栈空间不足逃逸
        • 3、动态类型逃逸
      • 03.如何避免
        • 1、避免返回指针
        • 2、使用值接收者
        • 3、避免在闭包中引用外部变量
        • 4、使用局部变量
    • 08.内存泄漏
    • 09.mutex锁原理
    • 10.Go常识
    • 50.Golang常犯错误
    • 51.常见坑1~10
  • Java

  • 业务问题

  • 关键技术

  • 项目常识

  • 计算机基础

  • 常识
  • GO
xiaonaiqiang
2021-06-03
目录

07.内存逃逸

# 01.内存逃逸

# 1、其他语言内存回收机制

  • 在C/C++开发中,动态分配内存(new/malloc)需要我们手动释放资源
  • 这样做的好处是,需要申请多少内存空间可以很好的掌握怎么分配
  • 但是这有个缺点,如果忘记释放内存,则会导致内存泄漏
  • 在很多高级语言中(python/Go/java)都加上了垃圾回收机制

# 2、什么是内存逃逸

  • 函数内部申请的临时变量,正常会分配到栈里,栈中的内存分配非常快,自动回收,无需垃圾回收
  • 但是若果申请的临时变量作为了函数返回值,编译器会认为在退出函数之后还有其他地方在引用
  • 在编译的时候就会将变量存储到堆中,堆中的数据不会自动回收,必须使用垃圾回收机制清除
  • 我们将这种 由于某些原因,数据没有分配到栈中而是分配到堆中的现象叫做 内存逃逸

# 3、内存分配堆和栈

# 1)内存分片概述

  • Go的垃圾回收,让堆和栈堆程序员保持透明

  • 真正解放了程序员的双手,让他们可以专注于业务,“高效”地完成代码编写

  • 把那些内存管理的复杂机制交给编译器

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

# 2)分配到栈里

  • 下面的例子,函数内部申请的临时变量,即使你是用make申请到的内存
  • 如果发现在退出函数后没有用了,那么就把丢到栈上,毕竟栈上的内存分配比堆上快很多
func F() {
	temp := make([]int, 0, 20)
	...
}
1
2
3
4

# 3)分配到堆里

  • 申请的代码和上面的一模一样,但是申请后作为返回值返回了
  • 编译器会认为在退出函数之后还有其他地方在引用,当函数返回之后并不会将其内存归还
  • 那么就申请到堆里
func F() {
	temp := make([]int, 0, 20)
	...
	return temp
}
1
2
3
4
5

# 4)分配到栈里和堆里区别

  • 分片到堆的坏处

    • 如果变量都分配到堆上,堆不像栈可以自动清理
    • 它会引起Go频繁地进行垃圾回收,而垃圾回收会占用比较大的系统开销
  • 放到堆还是栈的情况

    • 堆适合不可预知的大小的内存分配
    • 但是为此付出的代价是分配速度较慢,而且会形成内存碎片
    • 栈内存分配则会非常快,栈分配内存只需要两个CPU指令:“PUSH”和“RELEASE”分配和释放;
    • 而堆分配内存首先需要去找到一块大小合适的内存块之后要通过垃圾回收才能释放

# 02.逃逸的几种情况

# 0、逃逸分析

  • 逃逸分析是分析在程序的哪些地方可以访问到该指针
  • 简单来说,编译器会根据变量是否被外部引用来决定是否逃逸
1、如果函数外部没有引用,则优先放到栈中;
2、如果函数外部存在引用,则必定放到堆中;
1
2
  • 对此你可以理解为,逃逸分析是编译器用于决定变量分配到堆上还是栈上的一种行为

  • 注意:go 在编译阶段确立逃逸,并不是在运行时

# 1、指针逃逸

方法内把局部变量指针返回

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

  • 我们知道传递指针可以减少底层值的拷贝,可以提高效率

  • 但是如果拷贝的数据量小,由于指针传递会产生逃逸,逃逸可能存储到堆中

  • 存储到堆可能会增加GC的负担,所以传递指针不一定是高效的

# 2、栈空间不足逃逸

  • 当我们创建一个切片长度为10000时就会逃逸
  • 实际上当栈空间不足以存放当前对象时或无法判断当前切片长度时会将对象分配到堆中
  • slice 的背后数组被重新分配了,因为 append 时可能会超出其容量( cap )
  • slice 初始化的地方在编译时是可以知道的,它最开始会在栈上分配
  • 如果切片背后的存储要基于运行时的数据进行扩充,就会在堆上分配
go build -gcflags=-m
1
package main
import "fmt"

func main() {
	s := make([]int, 10000, 10000)
	fmt.Println(s)
}
1
2
3
4
5
6
7

# 3、动态类型逃逸

  • 很多函数参数为interface类型,比如 Println函数
func Println(a ...interface{}) (n int, err error)
1
  • 编译期间很难确定其参数的具体类型,也能产生逃逸

# 03.如何避免

# 1、避免返回指针

func createSlice() *[]int {
    slice := []int{1, 2, 3}
    return &slice             // 指向局部变量,逃逸到堆上
}

func createSlice() []int {
    return []int{1, 2, 3}    // 不需要逃逸到堆上
}
1
2
3
4
5
6
7
8

# 2、使用值接收者

func (s *LargeStruct) Process() {
    // 方法会修改结构体
}

//推荐:如果结构体较小且方法不需要修改其状态,使用值接收者
func (s SmallStruct) Process() {
    // 方法不会修改结构体
}
1
2
3
4
5
6
7
8

# 3、避免在闭包中引用外部变量

  • 闭包会捕获其创建环境中的变量,这可能导致这些变量逃逸到堆上
  • 尽量减少闭包的使用,或者将闭包限制在最小范围内
func main() {
    x := 10
    f := func() int {
       return x // 闭包捕获外部变量 x
    }
    fmt.Println(f())
}

//推荐:将闭包中的变量作为参数传递
func createClosure(x int) func() int {
    return func() int {
        return x
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 4、使用局部变量

  • 在函数中,尽量将变量声明为局部变量
func createBuffer() *bytes.Buffer {
    buffer := new(bytes.Buffer) // 临时对象,逃逸到堆上
    return buffer
}

//推荐:使用局部变量
func createBuffer() bytes.Buffer {
    return bytes.Buffer{}          // 使用局部变量
}

1
2
3
4
5
6
7
8
9
10
上次更新: 2025/2/25 11:09:45
06.channel
08.内存泄漏

← 06.channel 08.内存泄漏→

最近更新
01
04.数组双指针排序_子数组
03-25
02
08.动态规划
03-25
03
06.回溯算法
03-25
更多文章>
Theme by Vdoing | Copyright © 2019-2025 逍遥子 技术博客 京ICP备2021005373号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式