浅析 Golang 内存管理

news2025/5/15 14:01:00

文章目录

  • 浅析 Golang 内存管理
    • 栈(Stack)
    • 堆(Heap)
    • 堆 vs. 栈
    • 内存逃逸分析
      • 内存逃逸产生的原因
      • 避免内存逃逸的手段
    • 内存泄露
      • 常见的内存泄露场景
      • 如何避免内存泄露?
      • 总结

浅析 Golang 内存管理

在这里插入图片描述
在 Golang 当中,堆(Heap)和栈(Stack)是内存管理的两个核心区域,它们的用途、生命周期和管理方式有显著区别。

栈(Stack)

特点

  • 自动分配/释放:由编译器管理,无需开发者干预;
  • 高效:内存分配仅需移动栈指针;
  • 线程/协程私有:每一个 Goroutine 有自己独立的栈,可动态扩缩容;
  • 生命周期:与函数调用相绑定,函数返回时自动回收。

栈上保存的对象

  • 局部变量:函数内定义的普通变量(非指针、接口、逃逸对象);
  • 函数参数和返回值;
  • 值类型的临时变量;

逃逸分析
如果对象的引用逃逸到函数外部(如返回值是一个指针,或是函数当中的值被全局对象引用等),编译器会将其分配到堆上。

func escape() *int {
    x := 42 // x 逃逸到堆上(因为返回了指针)
    return &x
}

可通过 go build -gcflags="-m" 查看分析逃逸结果。

堆(Heap)

特点

  • 动态分配:运行时通过 GC 管理,存在分配和回收开销;
  • 共享访问:堆上的对象可以被多个 Goroutine 或函数引用;
  • 生命周期:由 GC 决定,当对象不可达时被回收。

堆上保存的对象
1)显式分配内存的对象:

p := new(int)    // 通过 new 分配在堆上
s := make([]int, 10) // 动态切片(底层数组在堆上)
m := make(map[string]int) // 映射(堆上)

2)逃逸对象:局部函数的返回值是指针或局部函数当中的局部变量被全局变量所引用。

func createUser() *User {
    u := User{Name: "Alice"} // 结构体逃逸到堆上
    return &u
}

3)大对象:超过栈容量限制的变量,比如大数组。
4)闭包捕获的变量:

func closure() func() {
    x := 42 // x 被闭包捕获,分配在堆上
    return func() { fmt.Println(x) }
}

5)接口和指针的动态类型:

var i interface{} = 42	// 接口背后的动态值可能分配在堆上

堆 vs. 栈

特性
管理方式由编译器管理运行时 GC 管理
速度极快(直接操作栈指针)较慢
线程安全每个 Goroutine 的栈空间独立全局共享
生命周期随函数调用结束自动回收由 GC 决定
适用对象小对象、短生命周期、未逃逸的局部变量大对象、逃逸对象(局部函数返回值为指针)、共享对象(局部函数创建的局部变量被全局变量引用)

内存逃逸分析

Golang 当中的内存逃逸指的主要是本应该被分配在栈上的对象,由于外部引用或生命周期延长,而不得不被分配到堆上。内存逃逸会增加 GC 的压力,进而影响到程序的性能。

内存逃逸产生的原因

1)对象被外部引用:

  • 函数返回局部变量的指针:由于 Golang 当中每个函数有自己的栈空间,函数当中新建的局部变量会优先分配到栈上,而当函数返回时,栈也会随之被销毁。如果函数返回的是局部变量的指针,为了确保局部变量不随着栈的销毁而被清除,它会被分配到堆上,从而产生了内存逃逸。
  • 被局部变量或包外引用:产生内存逃逸的原因同上,由于局部函数当中的局部变量被包外引用,那么为了确保该变量不随着函数返回时栈空间的销毁而被清除,需要被分配在堆上。

2)对象被闭包捕获:

  • 函数闭包捕获的变量会逃逸到堆上,原因是函数闭包指的是局部函数的返回值是另一个函数,函数返回后主函数的栈会被销毁,为了确保闭包函数仍然能使用主函数当中的变量,需要把被闭包捕获的变量分配到堆上。

3)动态类型或接口赋值:

  • 接口背后的动态值可能逃逸,原因在于接口类型在运行时的具体类型是不确定的。如果一个函数的返回值是接口值,由于返回值可能是指针,因此接口类型的变量应该分配在堆上。

4)栈空间不足时:

  • 超过栈容量的对象(比如大数组)会直接被分配到堆上。

5)反射或底层操作:

  • 使用 reflectunsafe 包可能导致逃逸分析失效。

避免内存逃逸的手段

  1. 减少指针暴露,比如将局部函数的返回值变为值类型;
  2. 避免返回局部变量的引用;
  3. 控制变量的作用域;
  4. 避免不必要的接口或反射;
  5. 利用编译器优化:通过 -gcflags="-m" 分析逃逸。

在 Geektutu 的博客上给出了一个「传值 vs. 传指针」的建议,在此也一并学习一下。

传值会拷贝整个对象,而传指针只传递对象的地址。传指针可以减少值的拷贝,但是会导致内存分配逃逸到堆中,增加 GC
的负担。在对象频繁创建和删除的场景下,传指针会导致 GC 开销变大从而影响性能。

一般情况下,对于需要修改原对象的值,或是占用内存较大的结构体,选择传指针。对于只读的占用内存较小的结构体,直接传值会获得更好的性能。

内存泄露

此处需要区分一个与「内存逃逸」名字非常像的概念,也就是「内存泄露」。二者有一定的关系,但本质上是不同的概念。内存泄露指的是程序在运行过程中未能正确释放不再使用的内存,导致堆内存持续增长(堆内存全局共享),最终可能耗尽系统资源。而内存逃逸指的是本应该分配在栈上的内存因为某种原因(比如被全局对象所引用,或局部变量被函数闭包捕获,或是返回值是地址)逃逸到了堆上。

常见的内存泄露场景

1)Goroutine 泄漏:指的是协程无法退出(如未关闭的 channel、死循环、阻塞 I/O 等)。

func leaky_goroutine_example() {
	ch := make(chan int)
	go func() {
		val := <- ch		// goroutine 阻塞地等待外部调用者传入一个值到 channel 当中
		fmt.Println(val)
	}()
	// ... ... ...
	// ⬆️ 如果最终没有值传入到 ch 当中, 那么之前启动的 goroutine 会一直阻塞, 导致内存泄漏
}

2)全局 mapslice 或缓存未清理旧的数据,导致旧数据一直占据着堆内存。

3)未关闭的资源:比如文件、数据库连接、HTTP 响应体未关闭。一个比较好的习惯是在打开这些资源之后紧跟一个 defer,确保资源被关闭。

4)循环引用。

如何避免内存泄露?

1)避免 Goroutine 泄漏:

  • 使用 context 控制 Goroutine 退出,通常与 select 相结合;
  • 确保 channel 被正确关闭,或有值写入。

2)使用 defer 及时释放资源。

3)HTTP 响应体必须关闭。

4)定期清理全局的 map 或使用 sync.Map

5)使用工具检测内存泄露:

  • pprof 监控内存使用;
  • runtime.ReadMemStats 检查内存增长。

总结

  • 内存逃逸是编译器行为,具体指的就是因为某些原因,本应该分配在栈上的内存逃逸到了堆上,内存逃逸会增加 GC 压力,但是没有内存泄露的风险;
  • 内存泄露是代码逻辑问题,需要手动优化(如积极使用 defer 关闭资源、使用 context 控制 goroutine 生命周期,使用 pprof 监控内存以避免长期运行的服务耗尽系统资源)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2376147.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++ 并发编程(1)再学习,为什么子线程不调用join方法或者detach方法,程序会崩溃? 仿函数的线程启动问题?为什么线程参数默认传参方式是值拷贝?

本文的主要学习点&#xff0c;来自 这哥们的视频内容&#xff0c;感谢大神的无私奉献。你可以根据这哥们的视频内容学习&#xff0c;我这里只是将自己不明白的点&#xff0c;整理记录。 C 并发编程(1) 线程基础&#xff0c;为什么线程参数默认传参方式是值拷贝&#xff1f;_哔…

【Python 算法零基础 2.模拟 ④ 基于矩阵】

目录 基于矩阵 Ⅰ、 2120. 执行所有后缀指令 思路与算法 ① 初始化结果列表 ② 方向映射 ③ 遍历每个起始位置 ④ 记录结果 Ⅱ、1252. 奇数值单元格的数目 思路与算法 ① 初始化矩阵 ② 处理每个操作 ③ 统计奇数元素 Ⅲ、 832. 翻转图像 思路与算法 ① 水平翻转图像 ② 像素值…

【教程】Docker方式本地部署Overleaf

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 目录 背景说明 下载仓库 初始化配置 修改监听IP和端口 自定义网站名称 修改数据存放位置 更换Docker源 更换Docker存储位置 启动Overleaf 创…

3337|3335. 字符串转换后的长度 I(||)

1.字符串转换后的长度 I 1.1题目 3335. 字符串转换后的长度 I - 力扣&#xff08;LeetCode&#xff09; 1.2解析 递推法解析 思路框架 我们可以通过定义状态变量来追踪每次转换后各字符的数量变化。具体地&#xff0c;定义状态函数 f(i,c) 表示经过 i 次转换后&#xff0…

PHP黑白胶卷底片图转彩图功能 V2025.05.15

关于底片转彩图 传统照片底片是摄影过程中生成的反色图像&#xff0c;为了欣赏照片&#xff0c;需要通过冲印过程将底片转化为正像。而随着数字技术的发展&#xff0c;我们现在可以使用数字工具不仅将底片转为正像&#xff0c;还可以添加色彩&#xff0c;重现照片原本的色彩效…

字符串检索算法:KMP和Trie树

目录 1.引言 2.KMP算法 3.Trie树 3.1.简介 3.2.Trie树的应用场景 3.3.复杂度分析 3.4.Trie 树的优缺点 3.5.示例 1.引言 字符串匹配&#xff0c;给定一个主串 S 和一个模式串 P&#xff0c;判断 P 是否是 S 的子串&#xff0c;即找到 P 在 S 中第一次出现的位置。暴力匹…

基于.Net开发的网络管理与监控工具

从零学习构建一个完整的系统 平常项目上线后&#xff0c;不仅意味着开发的完成&#xff0c;更意味着项目正式进入日常运维阶段。在这个阶段&#xff0c;网络的监控与管理也是至关重要的&#xff0c;这时候就需要一款网络管理工具&#xff0c;可以协助运维人员用于日常管理&…

Python并发编程:开启性能优化的大门(7/10)

1.引言 在当今数字化时代&#xff0c;Python 已成为编程领域中一颗璀璨的明星&#xff0c;占据着编程语言排行榜的榜首。无论是数据科学、人工智能&#xff0c;还是 Web 开发、自动化脚本编写&#xff0c;Python 都以其简洁的语法、丰富的库和强大的功能&#xff0c;赢得了广大…

易学探索助手-个人记录(十)

在现代 Web 应用中&#xff0c;用户体验的重要性不断上升。近期我完成了两个功能模块 —— 语音播报功能 与 用户信息修改表单&#xff0c;分别增强了界面交互与用户自管理能力。 一、语音播报功能&#xff08;SpeechSynthesis&#xff09; 功能特点 支持播放、暂停、继续、停…

学习51单片机01(安装开发环境)

新学期新相貌.......哈哈哈&#xff0c;我终于把贪吃蛇结束了&#xff0c;现在我们来学stc51单片机&#xff01; 要求&#xff1a;c语言的程度至少要到函数&#xff0c;指针尽量&#xff01;如果c语言不好的&#xff0c;可以回去看看我的c语言笔记。 1.开发环境的安装&#x…

SpringAI

机器学习&#xff1a; 定义&#xff1a;人工智能的子领域&#xff0c;通过数据驱动的方法让计算机学习规律&#xff0c;进行预测或决策。 核心方法&#xff1a; 监督学习&#xff08;如线性回归、SVM&#xff09;。 无监督学习&#xff08;如聚类、降维&#xff09;。 强化学…

lua 作为嵌入式设备的配置语言

从lua的脚本中获取数据 lua中栈的索引 3 | -1 2 | -2 1 | -3 可以在lua的解释器中加入自己自定的一些功能,其实没啥必要,就是为了可以练习下lua

ERP系统源码,小型工厂ERP系统源码,CRM+OA+进销存+财务

ERP系统源码&#xff0c;小型工厂ERP系统源码&#xff0c;ERP计划管理系统源码&#xff0c;CRMOA进销存财务 对于ERP来说&#xff0c;最为主要的作用就是能够强调企业的计划性&#xff0c;通过以业务订单以及客户的相关需求来作为企业计划的基础&#xff0c;并且还能够对企业现…

基于EFISH-SCB-RK3576/SAIL-RK3576的矿用本安型手持终端技术方案‌

&#xff08;国产化替代J1900的矿山智能化解决方案&#xff09; 一、硬件架构设计‌ ‌本安型结构设计‌ ‌防爆防护体系‌&#xff1a; 采用铸镁合金外壳复合防爆玻璃&#xff08;抗冲击能量>20J&#xff09;&#xff0c;通过GB 3836.1-2021 Ex ib I Mb认证 全密闭IP68接口…

配置文件介绍xml、json

#灵感# 常用xml&#xff0c; 但有点模棱两可&#xff0c;记录下AI助理给我总结的。 .xml XML&#xff08;eXtensible Markup Language&#xff0c;可扩展标记语言&#xff09;是一种用于存储和传输数据的标记语言。它与 HTML 类似&#xff0c;但有以下主要特点和用途&#xf…

【PostgreSQL数据分析实战:从数据清洗到可视化全流程】附录-D. 扩展插件列表(PostGIS/PostgREST等)

&#x1f449; 点击关注不迷路 &#x1f449; 点击关注不迷路 &#x1f449; 点击关注不迷路 文章大纲 附录D. PostgreSQL扩展插件速查表一、插件分类速查表二、核心插件详解三、安装与配置指南四、应用场景模板五、版本兼容性说明六、维护与优化建议七、官方资源与工具八、附录…

Qt笔记---》.pro中配置

文章目录 1、概要1.1、修改qt项目的中间文件输出路径和部署路径1.2、Qt 项目模块配置1.3、外部库文件引用配置 1、概要 1.1、修改qt项目的中间文件输出路径和部署路径 &#xff08;1&#xff09;、为解决 “ 输出文件 ” 和 “ 中间输出文件 ”全部在同一个文件夹下的问题&am…

【Liblib】基于LiblibAI自定义模型,总结一下Python开发步骤

一、前言 Liblib AI&#xff08;哩布哩布 AI&#xff09;是一个集成了先进人工智能技术和用户友好设计的 AI 图像创作绘画平台和模型分享社区。 强大的图像生成能力 &#xff1a;以 Stable Diffusion 技术为核心&#xff0c;提供文生图、图生图、图像后期处理等功能&#xff…

CCF第七届AIOps国际挑战赛季军分享(RAG)

分享CCF 第七届AIOps国际挑战赛的季军方案&#xff0c;从我们的比赛经历来看&#xff0c;并不会&#xff0c;相反&#xff0c;私域领域问答的优秀效果说明RAG真的很重要 历经4个月的时间&#xff0c;从初赛赛道第1&#xff0c;复赛赛道第2&#xff0c;到最后决赛获得季军&…

【Cesium入门教程】第七课:Primitive图元

Cesium丰富的空间数据可视化API分为两部分&#xff1a;primitive API面向三维图形开发者&#xff0c;更底层一些。 Entity API是数据驱动更高级一些。 // entity // 调用方便&#xff0c;封装完美 // 是基于primitive的封装// primitive // 更接近底层 // 可以绘制高级图形 /…