go语言内存泄漏的常见形式

go语言内存泄漏

在这里插入图片描述

子字符串导致的内存泄漏

使用自动垃圾回收的语言进行编程时,通常我们无需担心内存泄漏的问题,因为运行时会定期回收未使用的内存。但是如果你以为这样就完事大吉了,哪里就大错特措了。

因为,虽然go中并未对字符串时候共享底层内存块进行规定,但go语言编译器/运行时默认情况下允许字符串共享底层内存块,直到原先的字符串指向的内存被修改才会进行写时复制,这是一个很好的设计,既能节省内存,又能节省CPU资源,但有时也会导致"内存泄漏"。

例如如下代码,一旦调用demo就会导致将近1M内存的泄漏,因为s0只使用了50字节,但是会导致1M的内存一直无法被回收,这些内存会一直持续到下次s0被修改的时候才会被释放掉。

var s0 string // a package-level variable// A demo purpose function.
func f(s1 string) {s0 = s1[:50]// Now, s0 shares the same underlying memory block// with s1. Although s1 is not alive now, but s0// is still alive, so the memory block they share// couldn't be collected, though there are only 50// bytes used in the block and all other bytes in// the block become unavailable.
}func demo() {s := createStringWithLengthOnHeap(1 << 20) // 1M bytesf(s)
}

为了避免这种内存泄漏,我们可以使用[]byte来替代原先的1M大小的内存,不过这样会有两次50字节的内存重复

func f(s1 string) {s0 = string([]byte(s1[:50]))
}

当然我们也可以利用go编译器的优化来避免不必要的重复,只需要浪费一个字节内存就行

func f(s1 string) {s0 = (" " + s1[:50])[1:]
}

上述方法的缺点是编译器优化以后可能会失效,并且其他编译器可能无法提供该优化

避免此类内存泄漏的第三种方法是利用 Go 1.10 以来支持的 strings.Builder

import "strings"func f(s1 string) {var b strings.Builderb.Grow(50)b.WriteString(s1[:50])s0 = b.String()
}

从 Go 1.18 开始, strings 标准库包中新增了 Clone 函数,这成为了完成这项工作的最佳方式。

子切片导致的内存泄漏

同样场景下,切片也会导致内存的浪费

与子字符串类似,子切片也可能导致某种内存泄漏。在下面的代码中,调用 g 函数后,保存 s1 元素的内存块所占用的大部分内存将会丢失(如果没有其他值引用该内存块)。

var s0 []intfunc g(s1 []int) {// Assume the length of s1 is much larger than 30.s0 = s1[len(s1)-30:]
}

如果我们想避免这种内存泄漏,我们必须复制 s0 的 30 个元素,这样 s0 的活跃性就不会阻止收集承载 s1 元素的内存块。

func g(s1 []int) {s0 = make([]int, 30)copy(s0, s1[len(s1)-30:])// Now, the memory block hosting the elements// of s1 can be collected if no other values// are referencing the memory block.
}

未重置子切片指针导致的内存泄漏

在下面的代码中,调用 h 函数后,为切片 s 的第一个和最后一个元素分配的内存块将丢失。

func h() []*int {s := []*int{new(int), new(int), new(int), new(int)}// do something with s ...// 返回一个从1开始,不能到索引3的新切片, 也就是 s[1], s[2]return s[1:3:3]
}

只要返回的切片仍然有效,它就会阻止收集 s 的任何元素,从而阻止收集为 s 的第一个和最后一个元素引用的两个 int 值分配的两个内存块。

如果我们想避免这种内存泄漏,我们必须重置丢失元素中存储的指针。

func h() []*int {s := []*int{new(int), new(int), new(int), new(int)}// do something with s ...// Reset pointer values.s[0], s[len(s)-1] = nil, nilreturn s[1:3:3]
}

挂起Goroutine导致的内存泄漏

有时,Go 程序中的某些 goroutine 可能会永远处于阻塞状态。这样的 goroutine 被称为挂起的 goroutine。Go 运行时不会终止挂起的 goroutine,因此为挂起的 goroutine 分配的资源(以及它们引用的内存块)永远不会被垃圾回收。

Go 运行时不会杀死挂起的 Goroutine 有两个原因。一是 Go 运行时有时很难判断一个阻塞的 Goroutine 是否会被永久阻塞。二是我们有时会故意让 Goroutine 挂起。例如,有时我们可能会让 Go 程序的主 Goroutine 挂起,以避免程序退出。

如果不停止time.Ticker也会导致内存泄漏

time.Timer 值不再使用时,它会在一段时间后被垃圾回收。但 time.Ticker 值则不然。我们应该在 time.Ticker 值不再使用时停止它。

不正确地使用终结器会导致真正的内存泄漏

为属于循环引用组的成员值设置终结器(finalizer)可能会阻止为该循环引用组分配的所有内存块被回收。这是真正的内存泄漏,不是某种假象。

例如,在调用并退出以下函数后,分配给 xy 的内存块不能保证在未来的垃圾收集中被收集。

func memoryLeaking() {type T struct {v [1<<20]intt *T}var finalizer = func(t *T) {fmt.Println("finalizer called")}var x, y T// The SetFinalizer call makes x escape to heap.runtime.SetFinalizer(&x, finalizer)// The following line forms a cyclic reference// group with two members, x and y.// This causes x and y are not collectable.x.t, y.t = &y, &x // y also escapes to heap.
}

因此,请避免为循环引用组中的值设置终结器。

延迟函数调用导致的某种资源泄漏

非常大的延迟调用堆栈也可能会消耗大量内存,并且如果某些调用延迟太多,某些资源可能无法及时释放。

例如,如果在调用以下函数时需要处理许多文件,那么在函数退出之前将有大量文件处理程序无法释放。

func writeManyFiles(files []File) error {for _, file := range files {f, err := os.Open(file.path)if err != nil {return err}defer f.Close()_, err = f.WriteString(file.content)if err != nil {return err}err = f.Sync()if err != nil {return err}}return nil
}

对于这种情况,我们可以使用匿名函数来封装延迟调用,以便延迟函数调用能够更早地执行。例如,上面的函数可以重写并改进为

func writeManyFiles(files []File) error {for _, file := range files {if err := func() error {f, err := os.Open(file.path)if err != nil {return err}// The close method will be called at// the end of the current loop step.defer f.Close()_, err = f.WriteString(file.content)if err != nil {return err}return f.Sync()}(); err != nil {return err}}return nil
}

当然不要犯以下错误,需要有些同学将需要延时调用的函数字节省略,导致资源泄漏

_, err := os.Open(file.path)

如果是http请求,还会导致服务端挤压大量的连接无法释放

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

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

相关文章

es6学习02-let命令和const命令

一、let命令 1.let块级作用域&#xff1a; let关键字 VS var关键字 2.for循环计数器很适合let命令 var&#xff1a;整个for循环中一直都是同一个i在做1&#xff0c;最后输出的就是10&#xff1b; let&#xff1a;每循环一次都是多一个i的赋值&#xff0c;最后输出是可以调出…

MySQL深分页问题

在项目中有一个数据导出的需求&#xff0c;原来的实现方式也比较简单&#xff0c;根据查询条件分页查所有的数据&#xff0c;然后转成csv的格式一行一行写进文件存储中。 实际上线之后&#xff0c;发现出现了慢查询&#xff0c;具体的sql如下&#xff1a; select * from tabl…

前端面试宝典---创建对象的配置

Object.create 对整个对象的多个属性值进行配置 创建对象 不可更改属性值 // 创建对象 不可更改属性值 let obj Object.create({}, {name: {value: lisi,writable: false,},age: {value: 20,writable: true,} })console.log(初始化obj, obj) obj.name wangwu console.log(…

数据结构:C语言版严蔚敏和解析介绍,附pdf

《数据结构&#xff1a;C语言版&#xff08;第2版&#xff09;》严蔚敏李冬梅吴伟民.pdf 《数据结构&#xff1a;C语言版》严蔚敏&#xff0c;李冬梅.pdf 《数据结构C语言第2版习题解析与实验指导》李冬梅.pdf 「《数据结构&#xff1a;C语言版&#xff08;第2版 &#xff09;》…

深入理解 v-for 指令及其使用方法

在 Vue.js 中&#xff0c;v-for 是用于渲染列表的核心指令&#xff0c;它允许你通过循环渲染数据源中的每一项。通过 v-for&#xff0c;你可以轻松地将数组、对象或其他可迭代的数据渲染成 HTML 元素。本文将详细介绍 v-for 的基本用法、常见的应用场景、最佳实践及性能优化&am…

VIRT, RES,SHR之间的关系

VIRT、RES 和 SHR 是进程内存使用的三个关键指标&#xff0c;它们之间的关系反映了进程的内存分配和使用情况。以下是它们的定义和关系&#xff1a; VIRT&#xff08;虚拟内存&#xff09;&#xff1a;表示进程分配的虚拟内存总量&#xff0c;包括所有代码、数据、共享库、堆栈…

2025届蓝桥杯JavaB组个人题解(暂时不全,没题目)

2025 届蓝桥杯 Java B 组题解 第一次参加蓝桥杯&#xff0c;输入输出都用的BufferedReader和PrintWriter&#xff0c;怕输入输出不对或者内存超限&#xff0c;也怕出现小错误运行不了的&#xff0c;比如Main打成Mian什么的&#xff0c;但还是希望能拿省一&#xff0c;这里给出自…

在Vue项目的引入meting-js音乐播放器插件

开源项目&#xff1a;https://github.com/swzaaaaaaa/NBlog 1、开源项目中音乐播放插件的使用流程 步骤1&#xff1a;下载meting-js相关文件 在MetingJS官方仓库或其他可靠的CDN获取meting-js的JavaScript文件以及相关依赖&#xff08;如APlayer的文件&#xff09;。将它们下…

HTML应用指南:利用GET请求获取全国汉堡王门店位置信息

在当今快节奏的都市生活中&#xff0c;餐饮品牌的门店布局不仅反映了其市场策略&#xff0c;更折射出消费者对便捷、品质和品牌认同的追求。汉堡王&#xff08;Burger King&#xff09;作为全球知名的西式快餐品牌之一&#xff0c;在中国市场同样占据重要地位。自进入中国市场以…

使用 Function 来编写策略模式:优雅而高效的设计模式实践

引言&#xff1a;为什么选择策略模式&#xff1f; 策略模式&#xff08;Strategy Pattern&#xff09;是行为设计模式中的经典之一&#xff0c;它允许我们定义一系列的算法或操作&#xff0c;并使得它们可以互换使用。策略模式的关键思想是将算法的实现与使用它们的上下文分离…

Windows 系统中安装 Git 并配置 GitHub 账户

由于电脑重装系统&#xff0c;重新配置了git. 以下是在 Windows 系统中安装 Git 并配置 GitHub 账户的详细步骤&#xff1a; 1. 安装 Git 访问 Git 官网下载页面下载 Windows 版本的 Git 安装程序运行安装程序&#xff0c;使用默认选项即可 2. 配置 Git 用户信息 打开命令…

MergeX亮相GTC2025:开启全球广告流量交易新篇章

全球流量盛宴GTC2025深圳启幕&#xff0c;共探出海新蓝海 2025年4月24日至25日&#xff0c;GTC2025全球流量大会将在深圳福田会展中心9号馆隆重召开。作为跨境出海领域内规模最大、资源最丰富、产业链最完备的年度盛会&#xff0c;此次大会将汇聚众多行业精英&#xff0c;共同探…

kubernetes》》k8s》》Volume 数据卷 PVC PV NFS

为啥需要数据卷 容器磁盘上的文件的生命周期是短暂的&#xff0c;这就使得在容器中运行重要应用时会出现一些问题。首先&#xff0c;当容器崩溃时&#xff0c;kubelet会重启它&#xff0c;但是容器中的文件将丢失——容器以干净的状态&#xff08;镜像最初的状态&#xff09;重…

第十六届蓝桥杯 省赛C/C++ 大学B组

编程题目现在在洛谷上都可以提交了。 未完待续&#xff0c;写不动了。 C11 编译命令 g A.cpp -o A -Wall -lm -stdc11A. 移动距离 本题总分&#xff1a;5 分 问题描述 小明初始在二维平面的原点&#xff0c;他想前往坐标 ( 233 , 666 ) (233, 666) (233,666)。在移动过程…

谷歌怎么设置在新标签页中打开网页

按图示操作即可&#xff0c;藏得真深啊&#xff0c;无语&#xff0c;而且就算打开了&#xff0c;点收藏夹&#xff0c;顶部快捷栏里的网站&#xff0c;网站里的连接&#xff0c;打开也还是覆盖原来的&#xff0c;呵呵呵呵呵呵呵&#xff0c;有没有人管管 另外我的edge不知咋滴…

【企业级数据安全】掌握高性能Log4j2敏感信息脱敏方案

前言 在数据安全合规日益严格的今天&#xff0c;日志中的敏感信息保护已成为企业IT建设的必备环节。本文带您深入了解如何打造一套高性能、可实时配置的Log4j2日志脱敏插件&#xff0c;轻松应对各类敏感数据保护需求&#xff0c;让您的系统既满足合规要求&#xff0c;又不牺牲…

Linux中的tar -P选项

tar -P选项 Linux中的tar命令可用于文件和目录的归档以及压缩解压缩。而其中的-P选项是什么含义呢&#xff1f;下面我们就来看一看 1、不添加-P选项 对于如下压缩命令&#xff1a; tar -czvf pkg.tar.gz /opt/software执行该命名&#xff0c;控制台首行输出将会提示&#xf…

【2025年泰迪杯数据挖掘挑战赛】B题 详细解题思路+数据预处理+代码分享

目录 2025年泰迪杯B题详细解题思路问题一问题分析数学模型Python代码Matlab代码 问题二问题分析数学模型Python代码Matlab代码 问题三问题分析数学模型Python代码Matlab代码 问题四问题分析数学模型Python代码Matlab代码 2025年泰迪杯B题详细解题思路 初步分析整理了B题的赛题分…

SpringBoot3快速入门笔记

springboot3简介 SpringBoot 帮我们简单、快速地创建一个独立的、生产级别的 Spring 应用&#xff08;说明&#xff1a;SpringBoot底层是Spring&#xff09; 大多数 SpringBoot 应用只需要编写少量配置即可快速整合 Spring 平台以及第三方技术 特性&#xff1a; ● 快速创建…

记录centos8安装宝塔过程(两个脚本)

1、切换系统源&#xff08;方便使用宝塔安装脚本下载&#xff09; bash <(curl -sSL https://linuxmirrors.cn/main.sh) 2、宝塔安装脚本在宝塔的官网 宝塔面板下载&#xff0c;免费全能的服务器运维软件 根据自己的系统选择相应的脚本 urlhttps://download.bt.cn/insta…