浅析 Golang 内存管理
文章目录
- 浅析 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)反射或底层操作:
- 使用
reflect
或unsafe
包可能导致逃逸分析失效。
避免内存逃逸的手段
- 减少指针暴露,比如将局部函数的返回值变为值类型;
- 避免返回局部变量的引用;
- 控制变量的作用域;
- 避免不必要的接口或反射;
- 利用编译器优化:通过
-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)全局 map
、slice
或缓存未清理旧的数据,导致旧数据一直占据着堆内存。
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 监控内存以避免长期运行的服务耗尽系统资源)。
相关文章:

浅析 Golang 内存管理
文章目录 浅析 Golang 内存管理栈(Stack)堆(Heap)堆 vs. 栈内存逃逸分析内存逃逸产生的原因避免内存逃逸的手段 内存泄露常见的内存泄露场景如何避免内存泄露?总结 浅析 Golang 内存管理 在 Golang 当中,堆…...
记录: Windows下远程Liunx 系统xrdp 用到的一些小问题(免费踩坑 记录)
采用liunx Ubuntu22.04版本以下,需要安装 xrdp 或者VNC 具体过程就是下载 在linux命令行里 首先更新软件包:sudo apt update 安装xrdp服务:sudo apt install xrdp 启动XRDP:sudo systemctl start xrdp(如果在启动的…...

C++ 并发编程(1)再学习,为什么子线程不调用join方法或者detach方法,程序会崩溃? 仿函数的线程启动问题?为什么线程参数默认传参方式是值拷贝?
本文的主要学习点,来自 这哥们的视频内容,感谢大神的无私奉献。你可以根据这哥们的视频内容学习,我这里只是将自己不明白的点,整理记录。 C 并发编程(1) 线程基础,为什么线程参数默认传参方式是值拷贝?_哔…...

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

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

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

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

字符串检索算法:KMP和Trie树
目录 1.引言 2.KMP算法 3.Trie树 3.1.简介 3.2.Trie树的应用场景 3.3.复杂度分析 3.4.Trie 树的优缺点 3.5.示例 1.引言 字符串匹配,给定一个主串 S 和一个模式串 P,判断 P 是否是 S 的子串,即找到 P 在 S 中第一次出现的位置。暴力匹…...
Java大师成长计划之第22天:Spring Cloud微服务架构
📢 友情提示: 本文由银河易创AI(https://ai.eaigx.com)平台gpt-4o-mini模型辅助创作完成,旨在提供灵感参考与技术分享,文中关键数据、代码与结论建议通过官方渠道验证。 随着企业应用的不断扩展,…...
瀑布模型VS敏捷模型VS喷泉模型
目录 1. 瀑布模型(Waterfall Model) 2. 敏捷模型(Agile Model) 3. 喷泉模型(Fountain Model)...

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

Python并发编程:开启性能优化的大门(7/10)
1.引言 在当今数字化时代,Python 已成为编程领域中一颗璀璨的明星,占据着编程语言排行榜的榜首。无论是数据科学、人工智能,还是 Web 开发、自动化脚本编写,Python 都以其简洁的语法、丰富的库和强大的功能,赢得了广大…...
Linux 中 open 函数的本质与细节全解析
一、open简介 在 Linux 下,一切皆文件。而对文件的读写,离不开文件的“打开”操作。虽然 C 语言标准库提供了方便的 fopen,但更底层、更强大的是系统调用 open,掌握它能让你对文件系统控制更细致,在系统编程、驱动开发…...
llama.cpp无法使用gpu的问题
使用cuda编译llama.cpp后,仍然无法使用gpu。 ./llama-server -m ../../../../../model/hf_models/qwen/qwen3-4b-q8_0.gguf -ngl 40 报错如下 ggml_cuda_init: failed to initialize CUDA: forward compatibility was attempted on non supported HW warning: n…...
Python Unicode字符串和普通字符串转换
Unicode 是一种字符编码标准,旨在为世界上所有书写系统的每个字符提供一个唯一的数字标识(称为码点)。 码点: 每个 Unicode 字符被分配一个唯一的数字,称为码点表示形式:u 后跟 4-6 位十六进制数…...
Ansible Roles 是一种用于层次化和结构化组织 Ansible Playbook 的机制。
Ansible Roles 是一种用于层次化和结构化组织 Ansible Playbook 的机制。它通过将变量、文件、任务、模板和处理器等放置在单独的目录中,简化了 Playbook 的管理和复用。Roles 自 Ansible 1.2 版本引入,极大地提高了代码的可维护性和可重用性。 目录结构 一个标准的 Ansibl…...

易学探索助手-个人记录(十)
在现代 Web 应用中,用户体验的重要性不断上升。近期我完成了两个功能模块 —— 语音播报功能 与 用户信息修改表单,分别增强了界面交互与用户自管理能力。 一、语音播报功能(SpeechSynthesis) 功能特点 支持播放、暂停、继续、停…...
Linux基础 -- SSH 流式烧录与压缩传输笔记
Linux SSH 流式烧录与压缩传输指南 一、背景介绍 在嵌入式开发和维护中,常常需要通过 SSH 从 PC 向设备端传输大文件(如系统镜像、固件)并将其直接烧录到指定磁盘(如 /dev/mmcblk2)。然而,设备端存储空间…...

学习51单片机01(安装开发环境)
新学期新相貌.......哈哈哈,我终于把贪吃蛇结束了,现在我们来学stc51单片机! 要求:c语言的程度至少要到函数,指针尽量!如果c语言不好的,可以回去看看我的c语言笔记。 1.开发环境的安装&#x…...
事件驱动reactor的原理与实现
fdset 集合:(就是说) fd_set是一个位图(bitmap)结构 每个位代表一个文件描述符 0表示不在集合中,1表示在集合中 fd_set结构(简化): [0][1][2][3][4][5]...[1023] …...
大模型训练简介
在人工智能蓬勃发展的当下,大语言模型(LLM)成为了众多应用的核心驱动力。从智能聊天机器人到复杂的内容生成系统,LLM 的卓越表现令人瞩目。而这背后,大模型的训练过程充满了奥秘。本文将深入探讨 LLM 训练的各个方面&a…...
深度解析 MySQL 与 Spring Boot 长耗时进程:从故障现象到根治方案(含 Tomcat 重启必要性分析)
一、典型故障现象与用户痛点 在高并发业务场景中,企业级 Spring Boot 应用常遇到以下连锁故障: 用户侧:网页访问超时、提交表单无响应,报错 “服务不可用”。运维侧:监控平台报警 “数据库连接池耗尽”,To…...
More Effective C++:改善编程与设计(上)
More Effective C: 目录 More Effective C: 条款1:仔细区别pointers和 references 条款2:最好使用C转型操作符 条款3:绝对不要以多态方式处理数组 条款4:非必要不要提供default constructor 条款5:对定制的“类型转换函数”保持警觉 …...
TNNLS-2020《Autoencoder Constrained Clustering With Adaptive Neighbors》
核心思想分析 该论文提出了一种名为ACC_AN(Autoencoder Constrained Clustering with Adaptive Neighbors)的深度聚类方法,旨在解决传统子空间聚类方法在处理非线性数据分布和高维数据时的局限性。核心思想是将深度自编码器(Auto…...

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

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

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

基于EFISH-SCB-RK3576/SAIL-RK3576的矿用本安型手持终端技术方案
(国产化替代J1900的矿山智能化解决方案) 一、硬件架构设计 本安型结构设计 防爆防护体系: 采用铸镁合金外壳复合防爆玻璃(抗冲击能量>20J),通过GB 3836.1-2021 Ex ib I Mb认证 全密闭IP68接口…...

配置文件介绍xml、json
#灵感# 常用xml, 但有点模棱两可,记录下AI助理给我总结的。 .xml XML(eXtensible Markup Language,可扩展标记语言)是一种用于存储和传输数据的标记语言。它与 HTML 类似,但有以下主要特点和用途…...

【PostgreSQL数据分析实战:从数据清洗到可视化全流程】附录-D. 扩展插件列表(PostGIS/PostgREST等)
👉 点击关注不迷路 👉 点击关注不迷路 👉 点击关注不迷路 文章大纲 附录D. PostgreSQL扩展插件速查表一、插件分类速查表二、核心插件详解三、安装与配置指南四、应用场景模板五、版本兼容性说明六、维护与优化建议七、官方资源与工具八、附录…...