5分钟搞懂 Golang 堆内存

本文主要解释了堆内存的概念,介绍了 Linux 堆内存的工作原理,以及 Golang 如何管理堆内存。原文: Understanding Heap Memory in Linux with Go

alt

你想过为什么堆内存被称为 "堆" 吗?想象一下杂乱堆放的对象,与此类似,在计算机中,堆内存是动态分配和释放内存的空间,通常会导致内存块的无序排列。我们可以利用这种相似性和无序排列来理解堆内存,并探讨堆内存的概念及其在计算中的意义。

什么是堆内存?

堆内存是程序内存中用于动态内存分配的部分。堆内存不是在编译过程中预先确定的,而是在程序运行过程中动态管理的。程序在执行过程中可以根据需要从堆中申请、释放内存。

进程的内存布局

在继续介绍之前,我们先退一步,试着了解一下进程的内存布局,如下图所示,可以简单了解大致的内存布局。

+ - - - - - - - - - - - - - - - +
| Stack                         | ←- 栈,静态分配
| - - - - - - - - - - - - - - - | 
| Heap                          | ←- 堆,动态分配
| - - - - - - - - - - - - - - - | 
| Uninitialized Data            | ←- 未初始化数据
| - - - - - - - - - - - - - - - | 
| Initialized Data              | ←- 初始化数据
| - - - - - - - - - - - - - - - | 
| Code                          | ←- 代码(文本段)
+ - - - - - - - - - - - - - - - +

                     进程内存布局

我们来分解一下进程的内存布局,看看它们是如何协同工作的:

  • 栈(Stack):这部分内存用于静态内存分配,是存储局部变量和函数调用信息的地方,会随着函数的调用和返回而自动增大和缩小。

  • 堆(Heap):这是动态内存分配区域。当程序需要申请未预先定义的内存时,就会向堆申请空间。这里的内存可以在运行时分配和释放,为程序提供了处理数组、链表等动态数据结构所需的灵活性。

  • 未初始化数据(BSS 段):该段存放开发者已声明但并未初始化的全局变量和静态变量。程序启动时,操作系统会将这些变量初始化为零。

  • 初始化数据:该区域包含开发者已初始化的全局变量和静态变量。程序一开始运行,这些变量就可以立即使用。

  • 代码(文本段):该段存储程序的可执行指令。通常这部分内存是只读的,以防止意外修改程序指令。

通过简单介绍,可以看到内存是如何有效组织,以满足运行进程的静态和动态需求。堆的作用对于动态内存分配尤为重要,从而允许程序灵活高效的管理内存。

堆内存的特点

动态分配:内存在运行时申请、释放。 可变大小:分配的内存大小可以变化。 基于指针的管理:使用指针访问和控制内存。

下图演示了如何通过将堆内存划分为多个空闲块和已分配块来动态管理堆内存:

+ - - - - - - - - - - -+
| Heap Memory.         | ←- 堆内存
| - - - - - - - - - - -| 
| Free Block           | ←- 空闲块
| - - - - - - - - - - -| 
| Allocated Block 1    | ←- 已分配块1
| [Pointer -> Data]    |
| - - - - - - - - - - -| 
| Free Block           | ←- 空闲块
| - - - - - - - - - - -| 
| Allocated Block 2    | ←- 已分配块2
| [Pointer -> Data]    |
| - - - - - - - - - - -| 
| Free Block.          | ←- 空闲块
+ - - - - - - - - - - -+

                   动态分配
  • 空闲块(Free Blocks):这些是当前未分配的内存块,可供将来使用。当程序请求内存时,可以从这些空闲块中获取。

  • 已分配块(Allocated Blocks):这些部分已分配给程序并储存了数据。每个已分配块通常都包含一个指向其所含数据的指针。

多个空闲块和已分配块的存在表明,内存的分配和释放在程序运行过程中不断发生。由于内存分配和释放的时间不同,导致空闲内存段和已用内存段交替出现,堆就会出现这种碎片化现象。

堆内存如何工作?

堆内存由操作系统管理。当程序请求内存时,操作系统会从进程的堆内存段中分配内存。这一过程涉及多个关键组件和功能:

主要组成部分:

  1. 堆内存段:进程内存中保留用于动态分配的部分
  2. mmap:调整数据段末尾以增加或减少堆大小的系统调用
  3. malloc 和 free:C 库提供的函数,用于分配和释放堆上的内存
  4. 内存管理器:C 库的一个组件,用于管理堆,跟踪已分配和已释放的内存块。
Go 如何管理堆内存

Go 为堆内存管理提供了内置函数和数据结构,如 newmakeslicesmapschannels。这些函数和数据结构抽象掉了底层细节,在内部与操作系统的内存管理机制进行了交互。

实例

我们通过一个简单的 Go 程序来理解,该程序为整数片段分配内存、初始化数值并打印。

package main

import (
    "fmt"
    "runtime"
)

func main() {
    // 为包含10个整数的切片分配内存(动态数组)
    memorySize := 10
    slice := make([]int, memorySize)

    // 初始化并使用分配的内存
    for i := 0; i < len(slice); i++ {
        slice[i] = 5 // 为每个元素赋值
    }

    // 打印值
    for i := 0; i < len(slice); i++ {
        fmt.Printf("%d ", slice[i])
    }
    fmt.Println()

    // 通过强制垃圾收集演示内存释放
    runtime.GC()
}

为了了解 Go 如何与 Linux 内存管理库交互,可以使用 strace(我最喜欢的工具)来跟踪 Go 程序进行的系统调用。

内存分配中的系统调用
$ go build -o memory_allocation main.go
$ strace -f -e trace=mmap,munmap ./memory_allocation
mmap(NULL, 262144, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xffff94da0000
mmap(NULL, 131072, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xffff94d80000
mmap(NULL, 1048576, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xffff94c80000
mmap(NULL, 8388608, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xffff94400000
mmap(NULL, 67108864, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xffff90400000
mmap(NULL, 536870912, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xffff70400000
mmap(NULL, 536870912, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xffff50400000
mmap(0x4000000000, 67108864, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x4000000000
mmap(NULL, 33554432, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xffff4e400000
mmap(NULL, 68624, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xffff94c6f000
mmap(0x4000000000, 4194304, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x4000000000
mmap(0xffff94d80000, 131072, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xffff94d80000
mmap(0xffff94c80000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xffff94c80000
mmap(0xffff94402000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xffff94402000
mmap(0xffff90410000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xffff90410000
mmap(0xffff70480000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xffff70480000
mmap(0xffff50480000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xffff50480000
mmap(NULL, 1048576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xffff4e300000
mmap(NULL, 65536, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xffff94c5f000
mmap(NULL, 65536, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xffff94c4f000
strace: Process 1141999 attached
strace: Process 1142000 attached
strace: Process 1142001 attached
[pid 1141998] --- SIGURG {si_signo=SIGURG, si_code=SI_TKILL, si_pid=1141998, si_uid=0} ---
[pid 1142000] mmap(NULL, 262144, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xffff94c0f000
strace: Process 1142002 attached
5 5 5 5 5 5 5 5 5 5
[pid 1142001] mmap(NULL, 262144, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xffff4e2c0000
[pid 1141998] --- SIGURG {si_signo=SIGURG, si_code=SI_TKILL, si_pid=1141998, si_uid=0} ---
[pid 1142000] mmap(NULL, 65536, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xffff4e2b0000
[pid 1141998] mmap(NULL, 262144, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xffff4e270000
[pid 1142002] +++ exited with 0 +++
[pid 1142001] +++ exited with 0 +++
[pid 1142000] +++ exited with 0 +++
[pid 1141999] +++ exited with 0 +++
+++ exited with 0 +++
+ - - - - - - - - - - -+
| Go Program           | ←- Go 程序
| - - - - - - - - - - -| 
| Calls Go Runtime     | ←- 调用 Go 运行时
| - - - - - - - - - - -| 
| Uses syscalls:       | ←- 系统调用:mmap,munmap
| mmap, munmap         |
| - - - - - - - - - - -| 
| Interacts with OS    | ←- 与操作系统内存管理器交互
| Memory Manager       |
+ - - - - - - - - - - -+
                      系统调用的简化示例
strace 输出解释
  • mmap 调用mmap 系统调用用于分配内存页。输出中的每个 mmap 调用都是请求操作系统分配特定数量(用 size 参数指定,例如 262144、131072 字节)的内存,。

  • 内存保护(Memory Protections):参数 PROT_READ|PROT_WRITE 表示分配的内存应是可读和可写的。

  • 匿名映射(Anonymous Mapping)MAP_PRIVATE|MAP_ANONYMOUS 标记表示内存没有任何文件支持,所做更改对进程来说是私有的。

  • 固定地址映射(Fixed Address Mapping):有些 mmap 调用使用 MAP_FIXED 标记,指定内存应映射到特定地址,通常用于直接管理特定内存区域。

内存分配过程的各个阶段
+ - - - - - - - - - - -+
| Initialize Slice     | ←- 初始化切片
| [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] |
| - - - - - - - - - - -|
| Set Values           | ←- 设置值
| [5, 5, 5, 5, 5, 5, 5, 5, 5, 5] |
| - - - - - - - - - - -| 
| Print Values         | ←- 打印值
| 5 5 5 5 5 5 5 5 5 5  |
| - - - - - - - - - - -| 
| Force GC             | ←- 强制垃圾回收
| - - - - - - - - - - -|

上图说明了 Go 动态内存分配和管理的逐步过程。

  1. 初始化切片:
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

切片(动态数组)的初始状态为 10 个元素,全部设置为 0。这一步展示了 Go 如何为切片分配内存。

  1. 设置值:
[5, 5, 5, 5, 5, 5, 5, 5, 5, 5]

然后,在切片的每个元素中填入值 5。这一步演示了如何初始化和使用分配的内存。

  1. 打印值:
5 5 5 5 5 5 5 5 5 5

打印切片的值,确认内存分配和初始化成功。这一步验证程序是否正确访问和使用了分配的内存。

  1. 强制 GC(垃圾回收)

手动触发垃圾回收器,释放不再使用的内存。这一步强调 Go 的自动内存管理和清理过程,确保了资源的有效利用。

总结

堆内存是现代计算的重要方面,它实现了动态内存分配,使程序能在运行时有效管理内存。这种灵活性对于处理链表、树、图等动态数据结构至关重要,因为这些结构无法在编译时预先确定。了解堆内存对于开发人员编写高效、稳健的应用至关重要,可确保有效使用内存,并在不再需要时释放资源。

通过探讨堆内存在 Linux 中的工作原理以及 Go 如何管理动态内存分配,希望本文能为你提供有关内存管理内部运作的宝贵见解。掌握这些概念不仅有助于编写更好的代码,还有助于调试和优化应用程序。


你好,我是俞凡,在Motorola做过研发,现在在Mavenir做技术工作,对通信、网络、后端架构、云原生、DevOps、CICD、区块链、AI等技术始终保持着浓厚的兴趣,平时喜欢阅读、思考,相信持续学习、终身成长,欢迎一起交流学习。为了方便大家以后能第一时间看到文章,请朋友们关注公众号"DeepNoMind",并设个星标吧,如果能一键三连(转发、点赞、在看),则能给我带来更多的支持和动力,激励我持续写下去,和大家共同成长进步!

本文由 mdnice 多平台发布

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

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

相关文章

今日 AI 简报 | 开源 RAG 文本分块库、AI代理自动化软件开发框架、多模态统一生成框架、在线图像背景移除等

❤️ 如果你也关注大模型与 AI 的发展现状&#xff0c;且对大模型应用开发非常感兴趣&#xff0c;我会快速跟你分享最新的感兴趣的 AI 应用和热点信息&#xff0c;也会不定期分享自己的想法和开源实例&#xff0c;欢迎关注我哦&#xff01; &#x1f966; 微信公众号&#xff…

【C++学习(35)】在Linux中基于ucontext实现C++实现协程(Coroutine),基于C++20的co_await 协程的关键字实现协程

文章目录 为什么使用协程协程的理解协程优势协程的原语操作yield 与 resume 是一个switch操作&#xff08;三种实现方式&#xff09;&#xff1a; 基于 ucontext 的协程基于 XFiber 库的操作1 包装上下文2 XFiber 上下文调度器2.1 CreateFiber2.2 Dispatch 基于C20的co_return …

技术段子——论如何在0.387秒以内获取到闲鱼的上新数据。

个人一直在做闲鱼辅助相关的工具类软件。因为知道阿里系请求和风控的原因&#xff0c;再加个人做软件一直想的是如何让用户稳定运行。 因为阿里系对于请求的风控&#xff0c;所以个人风格导到软件效率一直一般。并不是做不到快速抓取&#xff0c;而是用效率换稳定。 所以&#…

【C#设计模式(10)——装饰器模式(Decorator Pattern)】

前言 装饰器模式可以在运行时为对象添加额外的功&#xff0c;而无需修改原始对象的代码。这种方式比继承更加灵活。 代码 //蛋糕类&#xff08;抽象类&#xff09; public abstract class Cake {public abstract void Create(); } //奶油蛋糕类 public class CreamCake : Cak…

2025年PMP考试安排是怎样?备考计划与重要时间节点公布

PMP考试在中国大陆每年举行四次&#xff0c;分别是在3月、6月、9月和12月。而中国港澳台地区的PMP考试则可以每天进行机考。在中国大陆地区的笔试考试中&#xff0c;主要采用涂卡和机读卡来记录成绩。 每次PMP考试的时间都是在周六的9点到12点50分&#xff0c;共计230分钟。 P…

缓冲式线程池C++简易实现

前言 : 代码也比较短&#xff0c;简单说一下代码结构&#xff0c;是这样的&#xff1a; SyncQueue.hpp封装了一个大小为MaxTaskCount的同步队列&#xff0c;这是一个模板类&#xff0c;它在线程池中承担了存放任务等待线程组中的线程来执行的角色。最底层是std::list<T>…

推荐一款功能强大的光学识别OCR软件:Readiris Dyslexic

Readiris Dyslexic是一款功能强大的光学识别OCR软件&#xff0c;可以扫描任何纸质文档并将其转换为完全可编辑的数字文件(Word&#xff0c;Excel&#xff0c;PDF)&#xff0c;然后用你喜欢的编辑器进行编辑。该软件提供了一种轻松创建&#xff0c;修改和签名PDF的完整解决方法&…

【面试全纪实 | Nginx 04】请回答,你真的精通Nginx吗?

&#x1f5fa;️博客地图 &#x1f4cd;1、location的作用是什么&#xff1f; &#x1f4cd;2、你知道漏桶流算法和令牌桶算法吗&#xff1f; &#x1f4cd;3、Nginx限流怎么做的&#xff1f; &#x1f4cd;4、为什么要做动静分离&#xff1f; &#x1f4cd;5、Nginx怎么做…

如何为你的 SaaS 公司做好国际化发展的准备?

随着 SaaS&#xff08;软件即服务&#xff09;公司的不断发展&#xff0c;确定扩张机会并建立可扩展的流程和策略以支持这些机会变得至关重要。一些公司向上游市场扩张&#xff0c;向企业销售产品&#xff0c;而此前他们主要面向中小企业。一些公司则朝着相反的方向发展&#x…

Towards Reasoning in Large Language Models: A Survey

文章目录 题目摘要引言什么是推理?走向大型语言模型中的推理测量大型语言模型中的推理发现与启示反思、讨论和未来方向 为什么要推理?结论题目 大型语言模型中的推理:一项调查 论文地址:https://arxiv.org/abs/2212.10403 项目地址: https://github.com/jeffhj/LM-reason…

推荐一款硬盘数据清除工具:Macrorit Data Wiper

Macrorit Data Wiper是一款硬盘数据清除工具&#xff0c;用于安全擦除数据、分区和磁盘的一站式工具包。完全擦除系统/引导分区。许多程序文件默认存储在系统磁盘驱动器中。如果您或您的组织想要永久擦除磁盘驱动器以防止未经授权使用您的数据&#xff0c;则此功能是必要的。 为…

第13章 Zabbix分布式监控企业实战

企业服务器对用户提供服务,作为运维工程师最重要的事情就是保证该网站正常稳定的运行,需要实时监控网站、服务器的运行状态,并且有故障及时去处理。 监控网站无需人工时刻去访问WEB网站或者登陆服务器去检查,可以借助开源监控软件例如Zabbix、Cacti、Nagios、Ganglia等来实…

2024IJCAI | MetalISP: 仅用1M参数的RAW到RGB高效映射模型

文章标题是&#xff1a;《MetaISP:Effcient RAW-to-sRGB Mappings with Merely 1M Parameters》 MetaISP收录于2024IJCAI&#xff0c;是新加坡国立大学&#xff08;Xinchao Wang为通讯作者&#xff09;和华为联合研发的新型ai-isp。 原文链接&#xff1a;MetaISP 【1】论文的…

使用 ts-node 运行 ts文件,启动 nodejs项目

最近在写一个nodejs项目&#xff0c;使用 ts-node 启动项目。遇到了一些问题&#xff0c;在此记录一下。 ts-node 是 TypeScript 执行引擎和 Node.js 的 REPL(一个简单的交互式的编程环境)。 它能够直接在 Node.js 上执行 TypeScript&#xff0c;而无需预编译。 这是通过挂接…

《鸿蒙生态:开发者的机遇与挑战》

一、引言 在当今科技飞速发展的时代&#xff0c;操作系统作为连接硬件与软件的核心枢纽&#xff0c;其重要性不言而喻。鸿蒙系统的出现&#xff0c;为开发者带来了新的机遇与挑战。本文将从开发者的角度出发&#xff0c;阐述对鸿蒙生态的认知和了解&#xff0c;分析鸿蒙生态的…

PHP代码审计 - SQL注入

SQL注入 正则搜索(update|select|insert|delete).*?where.*示例一&#xff1a; bluecms源码下载&#xff1a;source-trace/bluecms 以项目打开网站根目录&#xff0c;并以ctrlshiftf打开全局搜索 (update|select|insert|delete).*?where.*并开启正则匹配 最快寻找脆弱点的…

Essential Cell Biology--Fifth Edition--Chapter one (5)

1.1.4 The eukaryotic cell [真核细胞] 真核细胞&#xff0c;一般来说&#xff0c;比细菌和古细菌更大&#xff0c;更复杂。有些是独立的单细胞生物&#xff0c;如变形虫和酵母&#xff08;图1-14&#xff09;&#xff1b;另一些则生活在多细胞集合中。所有更复杂的多细胞生物…

线程-2-线程概念与控制

main 线程常见寄存器&#xff08;CR3 EIP IR MMU TLB&#xff09; CR3是当前进程页表物理内存地址&#xff08;包不能虚拟地址&#xff0c;不然套娃了&#xff09; CPU中有寄存器指向task_struct* current EIP&#xff1a;入口虚拟地址 IR&#xff1a;当前命令地址系统总线&a…

Vulkan 开发(十一):Vulkan 交换链

Vulkan 系列文章&#xff1a; 1. 开篇&#xff0c;Vulkan 概述 2. Vulkan 实例 3. Vulkan 物理设备 4. Vulkan 设备队列 5. Vulkan 逻辑设备 6. Vulkan 内存管理 7. Vulkan 缓存 8. Vulkan 图像 9. Vulkan 图像视图 10. Vulkan 窗口表面&#xff08;Surface&#xff…

【HarmonyOS】鸿蒙系统在租房项目中的项目实战(一)

从今天开始&#xff0c;博主将开设一门新的专栏用来讲解市面上比较热门的技术 “鸿蒙开发”&#xff0c;对于刚接触这项技术的小伙伴在学习鸿蒙开发之前&#xff0c;有必要先了解一下鸿蒙&#xff0c;从你的角度来讲&#xff0c;你认为什么是鸿蒙呢&#xff1f;它出现的意义又是…