GoLang内存泄漏原因排查详解

目录
  • 背景
  • 临时性内存泄漏
  • 通道理解

背景

Go 语言中有对应的Go 内存回收机制,在Go采用 并发三色标记清除  算法, 但是由于实际的过程中 发现会有一些内存泄漏的常见,内存泄漏 分为: 临时性 和 永久性内存泄漏。

初步排查过程中: 发现Linux使用top 发现内存随着时间会持续的增加没有稳定在一个合理值中。

在使用 pprof ,BBC 等 Go的内存泄漏工具进行排查

临时性内存泄漏

指的释放内存 不及时,对应的内存在更晚时候释放,这类问题主要是 string,slice 和底层的Buffer 错误共享,或者defer 函数资源没有及时的释放。

数组不正常使用导致内存泄漏

数组作为形参 遵守的是 值拷贝,如果函数调用次数很多并且数组过大,导致 内存使用激增

func countTragetBigNum(nums [1000000]int, target int) int {
   num := 0
   for i :=0; i< len(nums) && nums[i] ==target ; i++ {
   	num += 1
   }
   return num
}
# 在短时间内 创建100万数组 内存是8M,短时间内调用 100 次,并且作为是形参 在Go进行是 值Copy ,800M, 所以在时间过程中:

大数组在形参的场景下,通常使用的是切片方式。或者使用指针进行传递,避免在短时间内内存激增

goroutine 没有及时释放

一个 goroutine 创建 2KB

1. 互斥锁没有释放
func mutexTest() {
	mutex := sync.Mutex{}
	for i := 0; i < 10; i++ {
		go func () {
			mutex.Lock()
			fmt.Printf("%d goroutine get mutex", i)
			time.Sleep(100 * time.Microsecond)
		}()
	}
}
2. 死锁情形
3. 空channel --声明空channel但是没有初始化 导致读写被阻塞
func channelTest(){
	// 声明空 channel且不初始化导致阻塞
	var c chan int
	// 向通道中写数据
	go func() {
		c <- 1
		fmt.Printf("g1 send message")
	}()
	// 从通道中读数据
	go func(){
		<- c
		fmt.Printf("g2 receive message")
	}()
}
4. 能进不能出, 能出不能进 (写和读 通道量不一致)
导致大量的协程被阻塞
func channelTest() {
	// 声明空 channel且不初始化导致阻塞
	var c chan int
	// 10 个向通道中写数据,导致 写大于读量
	// 10个通道读数据,1个通道写数据也是一样的
	for i := 0; i < 10; i++ {
		go func() {
			c <- i
			fmt.Printf("g1 send message")
		}()
	}
	// 从通道中读数据
	go func() {
		<-c
		fmt.Printf("g2 receive message")
	}()
}
5. Time 定时器使用 定时器没有 手动Stop 会一直占有内存
func tickerTest() {
	ticker := time.NewTicker(time.Second * 20)
	go func() {
		for t := range ticker.C {
			fmt.Printf("ticker trigger")
		}
	}()
	time.Sleep(time.Second * 20)
	ticker.Stop()
}

通道理解

通道 channel 属于Go语言中首次提出的 运用 goroutine 的进行数据交换的 容器;

  • 缓冲通道属于一个元素队列, 有先入先出原则, 属于线程安全
  • 如果通道满了, 发送goroutine 会阻塞直到 read goroutine 进行接收操作
  • 如果通道是空的, 执行接收goroutine 阻塞直到 write goroutine 在通道上发送数据;
  • 通道既不满也不空,所以通道的缓冲区将发送和接收 goroutine 进行解耦操作

如果程序直到 缓冲区的容量, 可以调用 内置的  cap函数 cap(ch) -- 缓冲区容量
len(ch) -- 缓冲区内 元素个数

goroutine 泄漏: 使用一个无缓冲通道,两个慢的 goroutine 卡住,发送的响应的结果没有其他的goroutine 接收 这个情况属于 goroutine 泄漏;

如果读速度比较快,缓冲区存是没有意义的

到此这篇关于GoLang内存泄漏原因排查详解的文章就介绍到这了,更多相关Go内存泄漏内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Go map发生内存泄漏解决方法

    目录 正文 hamp 结构体代码 查看占用的内存数量 对于 map 内存泄漏的解法 正文 Go 程序运行时,有些场景下会导致进程进入某个“高点”,然后就再也下不来了. 比如,多年前曹大写过的一篇文章讲过,在做活动时线上涌入的大流量把 goroutine 数抬升了不少,流量恢复之后 goroutine 数也没降下来,导致 GC 的压力升高,总体的 CPU 消耗也较平时上升了 2 个点左右. 有一个 issue 讨论为什么 allgs(runtime 中存储所有 goroutine 的一个全局 sl

  • GoLang内存泄漏原因排查详解

    目录 背景 临时性内存泄漏 通道理解 背景 Go 语言中有对应的Go 内存回收机制,在Go采用 并发三色标记清除  算法, 但是由于实际的过程中 发现会有一些内存泄漏的常见,内存泄漏 分为: 临时性 和 永久性内存泄漏. 初步排查过程中: 发现Linux使用top 发现内存随着时间会持续的增加没有稳定在一个合理值中. 在使用 pprof ,BBC 等 Go的内存泄漏工具进行排查 临时性内存泄漏 指的释放内存 不及时,对应的内存在更晚时候释放,这类问题主要是 string,slice 和底层的Bu

  • Java基础之内存泄漏与溢出详解

    一.浅析 内存泄露( memory leak):是指程序在申请内存后,无法释放已申请的内存空间,多次内存泄露堆积后果很严重,内存迟早会被占光.内存泄漏最终会造成内存溢出. 内存溢出(out of memory) :是指程序在申请内存时,没有足够的内存空间供其使用 JVM中有一下几种内存空间: 栈内存(Stack):每个线程私有的. 堆内存(Heap):所有线程公用的. 方法区(Method Area):有点像以前常说的"进程代码段",这里面存放了每个加载类的反射信息.类函数的代码.编译

  • Golang 内存管理简单技巧详解

    目录 引言 预先分配切片 结构中的顺序字段 使用 map[string]struct{} 而不是 map[string]bool 引言 除非您正在对服务进行原型设计,否则您可能会关心应用程序的内存使用情况.内存占用更小,基础设施成本降低,扩展变得更容易/延迟. 尽管 Go 以不消耗大量内存而闻名,但仍有一些方法可以进一步减少消耗.其中一些需要大量重构,但很多都很容易做到. 预先分配切片 数组是具有连续内存的相同类型的集合.数组类型定义指定长度和元素类型.数组的主要问题是它们的大小是固定的——它们

  • Golang内存泄漏场景以及解决方案详析

    目录 1.字符串截取 2.切片截取引起子切片内存泄漏 3.没有重置丢失的子切片元素中的指针 4.函数数组传参 5.goroutine 6.定时器 1)time.After 2)timer.ticker 总结 1.字符串截取 func main() { var str0 = "12345678901234567890" str1 := str0[:10] } 以上代码,会有10字节的内存泄漏,我们知道,str0和str1底层共享内存,只要str1一直活跃,str0 就不会被回收,10字节

  • Golang Defer关键字特定操作详解

    Go语言中的defer关键字用于在函数返回前执行一些特定的操作.可以将defer看作是一种后置语句,在函数中的任何位置都可以使用. 下面是一个使用defer的例子: func foo() { defer fmt.Println("Done") fmt.Println("Hello") } 在上面的例子中,当函数foo被调用时,它会先输出"Hello",然后再输出"Done",因为"Done"被包装在defe

  • PHP内存溢出优化代码详解

    相信很多人做大批量数据导出和数据导入的时候,经常会遇到PHP内存溢出的问题,在解决了问题之后,总结了一些经验,整理成文章记录下. 优化点 1.优化SQL语句,避免慢查询,合理的建立索引,查询指定的字段,sql优化这块在此就不展开了. 2.查询的结果集为大对象时转数组处理,框架中一般有方法可以转,如Laravel中有toArray(),Yii2中有asArray(). 3.对于大数组进行数据切割处理,PHP函数有array_chunk().array_slice(). 4.对于大型的字符串和对象,

  • Java基础之Unsafe内存操作不安全类详解

    简介 Unsafe类使Java拥有了像C语言的指针一样操作内存空间的能力,直接操作内存就意味着 1.不受jvm管理,也就意味着无法被GC,需要我们手动GC,稍有不慎就会出现内存泄漏. 2.Unsafe的不少方法中必须提供原始地址(内存地址)和被替换对象的地址,偏移量要自己计算,一旦出现问题就是JVM崩溃级别的异常,会导致整个JVM实例崩溃,表现为应用程序直接crash掉. 3.直接操作内存,也意味着其速度更快,在高并发的条件之下能够很好地提高效率. Unsafe 类 public final c

  • docker.service启动报错的一次排查详解

    执行以下命令报错 systemctl restart docker 查看错误信息 systemctl status docker -l 报错信息如下: ● docker.service - Docker Application Container Engine Loaded: loaded (/usr/lib/systemd/system/docker.service; disabled; vendor preset: disabled) Drop-In: /etc/systemd/system

  • 深入Golang中的sync.Pool详解

    我们通常用golang来构建高并发场景下的应用,但是由于golang内建的GC机制会影响应用的性能,为了减少GC,golang提供了对象重用的机制,也就是sync.Pool对象池. sync.Pool是可伸缩的,并发安全的.其大小仅受限于内存的大小,可以被看作是一个存放可重用对象的值的容器. 设计的目的是存放已经分配的但是暂时不用的对象,在需要用到的时候直接从pool中取. 任何存放区其中的值可以在任何时候被删除而不通知,在高负载下可以动态的扩容,在不活跃时对象池会收缩. sync.Pool首先

  • Golang之sync.Pool使用详解

    前言 我们通常用 Golang 来开发并构建高并发场景下的服务,但是由于 Golang 内建的GC机制多少会影响服务的性能,因此,为了减少频繁GC,Golang提供了对象重用的机制,也就是使用sync.Pool构建对象池. sync.Pool介绍 首先sync.Pool是可伸缩的临时对象池,也是并发安全的.其可伸缩的大小会受限于内存的大小,可以理解为是一个存放可重用对象的容器.sync.Pool设计的目的就是用于存放已经分配的但是暂时又不用的对象,而且在需要用到的时候,可以直接从该pool中取.

随机推荐