Go 语言中的map和内存泄漏
map在内存中总是会增长;它不会收缩。因此,如果map导致了一些内存问题,你可以尝试不同的选项,比如强制 Go 重新创建map或使用指针。
在 Go 中使用map时,我们需要了解map增长和收缩的一些重要特性。让我们深入探讨这一点,以防止可能导致内存泄漏的问题。
首先,为了查看这个问题的一个具体例子,让我们设计一个场景,在这个场景中我们将使用以下map:
m := make(map[int][128]byte)
每个 m
的值都是一个包含 128 字节的数组。我们将执行以下操作:
- 分配一个空的map。
- 添加 100 万个元素。
- 删除所有元素,并运行垃圾回收(GC)。
在每个步骤之后,我们希望打印堆的大小(使用一个 printAlloc
实用函数)。这将展示这个示例在内存方面的行为方式:
func main() {n := 1_000_000m := make(map[int][128]byte)printAlloc()for i := 0; i < n; i++ { // Adds 1 million elementsm[i] = [128]byte{}}printAlloc()for i := 0; i < n; i++ { // Deletes 1 million elementsdelete(m, i)}runtime.GC() // Triggers a manual GCprintAlloc()runtime.KeepAlive(m) // Keeps a reference to m so that the map isn’t collected
}func printAlloc() {var m runtime.MemStatsruntime.ReadMemStats(&m)fmt.Printf("%d KB\n", m.Alloc/1024)
}
我们分配一个空的map,添加 100 万个元素,删除 100 万个元素,然后运行垃圾回收。我们还确保使用 runtime.KeepAlive
保持对map的引用,以防止map被收集。让我们运行这个示例:
0 MB <-- After m is allocated
461 MB <-- After we add 1 million elements
293 MB <-- After we remove 1 million elements
我们观察到了什么?起初,堆大小很小。然后,在将 100 万个元素添加到map后,它显著增长了。但是,如果我们期望在删除所有元素后堆大小会减小,这并不是 Go 中map的工作方式。最后,尽管 GC 已经收集了所有元素,但堆大小仍然是 293 MB。因此,内存缩小了,但并非我们可能预期的方式。这其中的原理是什么?我们需要深入了解一下 Go 中map的工作原理。
map提供了一个无序的键值对集合,其中所有的键都是唯一的。在 Go 中,map基于哈希表数据结构:一个数组,其中每个元素都是指向键值对存储桶的指针,如图1所示。
图1 — 哈希表示例,重点关注存储桶 0。
每个存储桶都是一个固定大小的数组,包含八个元素。如果要将元素插入已经满了的存储桶(即存储桶溢出),Go 会创建另一个包含八个元素的存储桶,并将前一个存储桶链接到它上。图2显示了一个例子:
图2 — 如果存储桶溢出,Go 会分配一个新的存储桶,并将前一个存储桶链接到它上。
在底层,Go 中的map是指向 runtime.hmap
结构体的指针。该结构体包含多个字段,其中包括一个 B 字段,表示map中存储桶的数量:
type hmap struct {B uint8 // log_2 of # of buckets// (can hold up to loadFactor * 2^B items)// ...
}
在添加了100万个元素之后,B 的值等于18,这意味着有 2¹⁸ = 262,144 个存储桶。当我们删除了100万个元素后,B 的值是多少呢?仍然是18。因此,map仍然包含相同数量的存储桶。
原因在于map中存储桶的数量是不可缩减的。因此,从map中删除元素不会影响现有存储桶的数量;它只是将存储桶中的槽清零。map只能增长并拥有更多的存储桶;它永远不会缩小。
在先前的示例中,我们从461 MB减少到了293 MB,因为元素被收集,但运行垃圾回收并没有影响map本身。即使额外存储桶的数量(因为溢出而创建的存储桶)也保持不变。
让我们退一步,讨论map无法缩小的情况何时可能成为问题。想象一下使用 map[int][128]byte
来构建缓存。这个map以每个客户ID(int
)为键,保存一个长度为128字节的序列。现在,假设我们想保存最近的1000位客户。map的大小将保持不变,所以我们不必担心map无法缩小的问题。
但是,假设我们想要存储一小时的数据。同时,我们的公司决定在黑色星期五进行大促销:在一个小时内,我们可能会有数百万的客户连接到我们的系统。但是在黑色星期五之后的几天,我们的map将包含与高峰期相同数量的存储桶。这就解释了为什么在这种情况下我们可能会遇到内存消耗高却不会显著减少的情况。
如果我们不想手动重启服务来清理map消耗的内存量,有哪些解决方案?一种解决方案可以是定期重新创建当前map的副本。例如,每小时我们可以构建一个新map,复制所有元素,并释放先前的map。这种选择的主要缺点是,在复制后直到下一次垃圾回收之前,我们可能会在短时间内消耗两倍于当前内存。
另一种解决方案是将map类型更改为存储数组指针:map[int]*[128]byte
。这并没有解决我们会有大量存储桶的问题;然而,每个存储桶条目将为值保留指针的大小,而不是128字节(64位系统上为8字节,32位系统上为4字节)。
回到原始场景,让我们比较每种map类型在每个步骤后的内存消耗。以下表格显示了比较。
Step | map[int][128]byte | map[int]*[128]byte |
---|---|---|
分配一个空的 map | 0 MB | 0 MB |
添加100万个元素 | 461 MB | 182 MB |
删除所有元素并运行GC | 293 MB | 38 MB |
正如我们所看到的,在删除所有元素后,使用 map[int]*[128]byte
类型所需的内存量明显较少。此外,在这种情况下,由于一些优化措施以减少内存消耗,高峰时期所需的内存量也较少显著。
注意:如果键或值超过128字节,Go 将不会直接将其存储在map存储桶中。相反,Go 将存储用于引用键或值的指针。
结论
正如我们所见,向map添加 n 个元素,然后删除所有元素意味着在内存中保持相同数量的存储桶。因此,我们必须记住,由于 Go map只能增长,因此其内存消耗也会随之增加。它没有自动化的策略来缩小。如果这导致内存消耗过高,我们可以尝试不同的选项,比如强制 Go 重新创建map或使用指针来检查是否可以进行优化。
相关文章:

Go 语言中的map和内存泄漏
map在内存中总是会增长;它不会收缩。因此,如果map导致了一些内存问题,你可以尝试不同的选项,比如强制 Go 重新创建map或使用指针。 在 Go 中使用map时,我们需要了解map增长和收缩的一些重要特性。让我们深入探讨这一点…...

前缀和(c++,超详细,含二维)
前缀和与差分 当给定一段整数序列a1,a2,a3,a4,a5…an; 每次让我们求一段区间的和,正常做法是for循环遍历区间起始点到结束点,进行求和计算,但是当询问次数很多并且区间很长的时候 比如,10^5 个询问和10^6区间长度,相…...
详解FreeRTOS:二值信号量和计数信号量(高级篇—2)
目录 1、二值信号量 1.1、二值信号量运行机制 1.2、创建二值信号量 1...

持续集成交付CICD:Jenkins通过API触发流水线
目录 一、理论 1.HTTP请求 2.调用接口的方法 3.HTTP常见错误码 二、实验 1.Jenkins通过API触发流水线 三、问题 1.如何拿到上一次jenkinsfile文件进行自动触发流水线 一、理论 1.HTTP请求 (1)概念 HTTP超文本传输协议,是确保服务器…...

【Python】12 GPflow安装
概述 GPflow 是一个基于TensorFlow 在 Python 中构建高斯过程模型的包。高斯过程是一种监督学习模型。 高斯过程的一些优点是: 不确定性是高斯过程的固有部分。高斯过程可以在不知道答案时告诉您。适用于小型数据集。如果您的数据有限,高斯过程可以从…...
Ubuntu源码编译gdal3.6.2
在华为云申请了一台Ubuntu v18的机器,乱七八糟的不要装。 apt install build-essential pkg-config -y cmake-3.21.1 apt-get install openssl libssl-dev 过程参考:Yukon for PostgreSQL_格來羙、日出的博客-CSDN博客 zlib-1.2.9(不需要) 如果用系统的后面gd…...
【LeetCode】160. 相交链表
160. 相交链表 难度:简单 题目 给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。 图示两个链表在节点 c1 开始相交: 题目数据 保证 整个链式结构中…...

数据集笔记:NGSIM (next generation simulation)
1 数据集介绍 数据介绍s Next Generation Simulation (NGSIM) Open Data (transportation.gov) 数据地址:Next Generation Simulation (NGSIM) Vehicle Trajectories and Supporting Data | Department of Transportation - Data Portal 时间2005年到2006年间地…...

解决docker运行elastic服务端启动不成功
现象: 然后查看docker日志,发现有vm.max_map_count报错 ERROR: [1] bootstrap checks failed [1]: max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144] 解决办法: 1. 宿主机(运行doc…...
mysql数据库中mysql database 数据被破坏产生的一系列问题
在执行sql脚本时,没有注意到sql脚本文件包含了对mysql 原始数据库的操作,执行了脚本。 脚本执行成功之后,登录或链接数据库查看数据时报错: The user specified as a definer (‘mysql.infoschema’‘localhost’) does not exis…...

基于变形卷积和注意机制的带钢表面缺陷快速检测网络DCAM-Net(论文阅读笔记)
原论文链接->DCAM-Net: A Rapid Detection Network for Strip Steel Surface Defects Based on Deformable Convolution and Attention Mechanism | IEEE Journals & Magazine | IEEE Xplore DCAM-Net: A Rapid Detection Network for Strip Steel Surface Defects Base…...

05-Spring Boot工程中简化开发的方式Lombok和dev-tools
简化开发的方式Lombok和dev-tools Lombok常用注解 Lombok用标签方式代替构造器、getter/setter、toString()等重复代码, 在程序编译的时候自动生成这些代码 注解名功能NoArgsConstructor生成无参构造方法AllArgsConstructor生产含所有属性的有参构造方法,如果不希望含所有属…...

AIGC 技术在淘淘秀场景的探索与实践
本文介绍了AIGC相关领域的爆发式增长,并探讨了淘宝秀秀(AI买家秀)的设计思路和技术方案。文章涵盖了图像生成、仿真形象生成和换背景方案,以及模型流程串联等关键技术。 文章还介绍了淘淘秀的使用流程和遇到的问题及处理方法。最后,文章展望…...

ANSYS网格无关性检查
网格精度对应力结果存在很大的影响,有时候可以发现,随着网格精度逐渐提高,所求得的最大应力值逐渐趋于收敛。 默认网格: 从默认网格下计算出的应力云图可以发现,出现了的三处应力奇异点,此时算出的应力值是…...

设计模式-责任链-笔记
动机(Motivation) 在软件构建过程中,一个请求可能被多个对象处理,但是每个请求在运行时只能有个接受者,如果显示指定,将必不可少地带来请求者与接受者的紧耦合。 如何使请求的发送者不需要指定具体的接受…...
SpringMvc请求原理流程
springmvc是用户和服务沟通的桥梁,官网提供了springmvc的全面使用和解释:DispatcherServlet :: Spring Framework 流程 1.Tomcat启动 2.解析web.xml文件,根据servlet-class找到DispatcherServlet,根据init-param来获取spring的…...

【开源】基于Vue.js的音乐偏好度推荐系统的设计和实现
项目编号: S 012 ,文末获取源码。 \color{red}{项目编号:S012,文末获取源码。} 项目编号:S012,文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、系统设计2.1 功能模块设计2.1.1 音乐档案模块2.1…...

采集1688整店商品(店铺所有商品、店铺列表api)
返回数据: 请求链接 {"user": [],"items": {"item": [{"num_iid": "738354436678","title": "国产正品i13 promax全网通5G安卓智能手机源头厂家批发手机","pic_url": "http…...

IObit Unlocker丨解除占用程序软件
更多内容请收藏:https://rwx.tza-3.xyz 官网:IObit Unlocker “永远不用担心电脑上无法删除的文件。” 界面简单,支持简体中文,一看就会,只需要把无法删除/移动的文件或整个U盘拖到框里就行。 解锁率很高,…...

开发一款小程序游戏需要多少钱?
小程序游戏的开发成本因多种因素而异,无法提供具体的固定数字。以下是影响小程序游戏开发成本的一些关键因素: 游戏规模和复杂度: 小程序游戏可以是简单的休闲游戏,也可以是更复杂的策略游戏。规模和复杂度会影响开发所需的时间和…...

日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻
在如今就业市场竞争日益激烈的背景下,越来越多的求职者将目光投向了日本及中日双语岗位。但是,一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧?面对生疏的日语交流环境,即便提前恶补了…...
Ubuntu系统下交叉编译openssl
一、参考资料 OpenSSL&&libcurl库的交叉编译 - hesetone - 博客园 二、准备工作 1. 编译环境 宿主机:Ubuntu 20.04.6 LTSHost:ARM32位交叉编译器:arm-linux-gnueabihf-gcc-11.1.0 2. 设置交叉编译工具链 在交叉编译之前&#x…...
vue3 定时器-定义全局方法 vue+ts
1.创建ts文件 路径:src/utils/timer.ts 完整代码: import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...

2025盘古石杯决赛【手机取证】
前言 第三届盘古石杯国际电子数据取证大赛决赛 最后一题没有解出来,实在找不到,希望有大佬教一下我。 还有就会议时间,我感觉不是图片时间,因为在电脑看到是其他时间用老会议系统开的会。 手机取证 1、分析鸿蒙手机检材&#x…...
【服务器压力测试】本地PC电脑作为服务器运行时出现卡顿和资源紧张(Windows/Linux)
要让本地PC电脑作为服务器运行时出现卡顿和资源紧张的情况,可以通过以下几种方式模拟或触发: 1. 增加CPU负载 运行大量计算密集型任务,例如: 使用多线程循环执行复杂计算(如数学运算、加密解密等)。运行图…...

什么是Ansible Jinja2
理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具,可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板,允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板,并通…...

人机融合智能 | “人智交互”跨学科新领域
本文系统地提出基于“以人为中心AI(HCAI)”理念的人-人工智能交互(人智交互)这一跨学科新领域及框架,定义人智交互领域的理念、基本理论和关键问题、方法、开发流程和参与团队等,阐述提出人智交互新领域的意义。然后,提出人智交互研究的三种新范式取向以及它们的意义。最后,总结…...
华为OD最新机试真题-数组组成的最小数字-OD统一考试(B卷)
题目描述 给定一个整型数组,请从该数组中选择3个元素 组成最小数字并输出 (如果数组长度小于3,则选择数组中所有元素来组成最小数字)。 输入描述 行用半角逗号分割的字符串记录的整型数组,0<数组长度<= 100,0<整数的取值范围<= 10000。 输出描述 由3个元素组成…...

李沐--动手学深度学习--GRU
1.GRU从零开始实现 #9.1.2GRU从零开始实现 import torch from torch import nn from d2l import torch as d2l#首先读取 8.5节中使用的时间机器数据集 batch_size,num_steps 32,35 train_iter,vocab d2l.load_data_time_machine(batch_size,num_steps) #初始化模型参数 def …...

二维数组 行列混淆区分 js
二维数组定义 行 row:是“横着的一整行” 列 column:是“竖着的一整列” 在 JavaScript 里访问二维数组 grid[i][j] 表示 第i行第j列的元素 let grid [[1, 2, 3], // 第0行[4, 5, 6], // 第1行[7, 8, 9] // 第2行 ];// grid[i][j] 表示 第i行第j列的…...