golang容易导致内存泄漏的6种情况汇总

目录
  • 1. 定时器使用不当
    • 1.1 time.After()的使用
    • 1.2 time.NewTicker资源未及时释放
  • 2. select阻塞
    • 2.1 导致goroutine阻塞的情况
    • 2.2 循环空转导致CPU暴涨
  • 3. channel阻塞
  • 4. goroutine导致的内存泄漏
    • 4.1 申请过多的goroutine
    • 4.2 goroutine阻塞
      • 4.2.1 I/O问题
      • 4.2.2 互斥锁未释放
      • 4.2.3 死锁
      • 4.2.4 waitgroup使用不当
  • 5. slice 引起的内存泄漏
  • 6. 数组的值传递
  • 总结

1. 定时器使用不当

1.1 time.After()的使用

默认的time.After()是会有内存泄露问题的,因为每次time.After(duration x)会产生NewTimer(),在duration x到期之前,新创建的timer不会被GC,到期之后才会GC。

随着时间推移,尤其是duration x很大的话,会产生内存泄露的问题,应特别注意

for true {
	select {
	case <-time.After(time.Minute * 3):
    // do something
  default:
		time.Sleep(time.Duration(1) * time.Second)
	}
}

为了保险起见,使用NewTimer()或者NewTicker()代替的方式主动释放资源,两者的区别请自行查阅或看我往期文章https://blog.csdn.net/weixin_38299404/article/details/119352884

timer := time.NewTicker(time.Duration(2) * time.Second)
defer timer.Stop()
for true {
	select {
	case <-timer.C:
		// do something
	default:
		time.Sleep(time.Duration(1) * time.Second)
	}
}

1.2 time.NewTicker资源未及时释放

在使用time.NewTicker时需要手动调用Stop()方法释放资源,否则将会造成永久性的内存泄漏

timer := time.NewTicker(time.Duration(2) * time.Second)
// defer timer.Stop()
for true {
	select {
	case <-timer.C:
		// do something
	default:
		time.Sleep(time.Duration(1) * time.Second)
	}
}

2. select阻塞

使用select时如果有case没有覆盖完全的情况且没有default分支进行处理,最终会导致内存泄漏

2.1 导致goroutine阻塞的情况

func main() {
    ch1 := make(chan int)
    ch2 := make(chan int)
    ch3 := make(chan int)
    go Getdata("https://www.baidu.com",ch1)
    go Getdata("https://www.baidu.com",ch2)
    go Getdata("https://www.baidu.com",ch3)
    select{
        case v:=<- ch1:
            fmt.Println(v)
        case v:=<- ch2:
            fmt.Println(v)
    }
}

上述这种情况会阻塞在ch3的消费处导致内存泄漏

2.2 循环空转导致CPU暴涨

func main() {
	fmt.Println("main start")
	msgList := make(chan int, 100)
	go func() {
		for {
			select {
			case <-msgList:
			default:

			}
		}
	}()

	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt, os.Kill)
	s := <-c

	fmt.Println("main exit.get signal:", s)
}

上述for循环条件一旦命中default则会出现循环空转的情况,并最终导致CPU暴涨

3. channel阻塞

channel阻塞主要分为写阻塞和读阻塞两种情况

空channel

func channelTest() {
  	//声明未初始化的channel读写都会阻塞
    var c chan int
  	//向channel中写数据
    go func() {
        c <- 1
        fmt.Println("g1 send succeed")
        time.Sleep(1 * time.Second)
    }()
  	//从channel中读数据
    go func() {
        <-c
        fmt.Println("g2 receive succeed")
        time.Sleep(1 * time.Second)
    }()
    time.Sleep(10 * time.Second)
}

写阻塞

无缓冲channel的阻塞通常是写操作因为没有读而阻塞

func channelTest() {
    var c = make(chan int)
  	//10个协程向channel中写数据
    for i := 0; i < 10; i++ {
        go func() {
            <- c
            fmt.Println("g1 receive succeed")
            time.Sleep(1 * time.Second)
        }()
    }
  	//1个协程丛channel读数据
    go func() {
        c <- 1
        fmt.Println("g2 send succeed")
        time.Sleep(1 * time.Second)
    }()
  	//会有写的9个协程阻塞得不到释放
    time.Sleep(10 * time.Second)
}

有缓冲的channel因为缓冲区满了,写操作阻塞

func channelTest() {
    var c = make(chan int, 8)
  	//10个协程向channel中写数据
    for i := 0; i < 10; i++ {
        go func() {
            <- c
            fmt.Println("g1 receive succeed")
            time.Sleep(1 * time.Second)
        }()
    }
  	//1个协程丛channel读数据
    go func() {
        c <- 1
        fmt.Println("g2 send succeed")
        time.Sleep(1 * time.Second)
    }()
  	//会有写的几个协程阻塞写不进去
    time.Sleep(10 * time.Second)
}

读阻塞

期待从channel读数据,结果没有goroutine往进写数据

func channelTest() {
   var c = make(chan int)
  //1个协程向channel中写数据
  go func() {
    <- c
    fmt.Println("g1 receive succeed")
    time.Sleep(1 * time.Second)
  }()
  //10个协程丛channel读数据
  for i := 0; i < 10; i++ {
    go func() {
        c <- 1
        fmt.Println("g2 send succeed")
        time.Sleep(1 * time.Second)
    }()
  }
  //会有读的9个协程阻塞得不到释放
  time.Sleep(10 * time.Second)
}

4. goroutine导致的内存泄漏

4.1 申请过多的goroutine

例如在for循环中申请过多的goroutine来不及释放导致内存泄漏

4.2 goroutine阻塞

4.2.1 I/O问题

I/O连接未设置超时时间,导致goroutine一直在等待,代码会一直阻塞。

4.2.2 互斥锁未释放

goroutine无法获取到锁资源,导致goroutine阻塞

//协程拿到锁未释放,其他协程获取锁会阻塞
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.Millisecond)
        }()
    }
    time.Sleep(10 * time.Second)
}

4.2.3 死锁

当程序死锁时其他goroutine也会阻塞

func mutexTest() {
    m1, m2 := sync.Mutex{}, sync.RWMutex{}
  	//g1得到锁1去获取锁2
    go func() {
        m1.Lock()
        fmt.Println("g1 get m1")
        time.Sleep(1 * time.Second)
        m2.Lock()
        fmt.Println("g1 get m2")
    }()
    //g2得到锁2去获取锁1
    go func() {
        m2.Lock()
        fmt.Println("g2 get m2")
        time.Sleep(1 * time.Second)
        m1.Lock()
        fmt.Println("g2 get m1")
    }()
  	//其余协程获取锁都会失败
    go func() {
        m1.Lock()
        fmt.Println("g3 get m1")
    }()
    time.Sleep(10 * time.Second)
}

4.2.4 waitgroup使用不当

waitgroup的Add、Done和wait数量不匹配会导致wait一直在等待

5. slice 引起的内存泄漏

当两个slice 共享地址,其中一个为全局变量,另一个也无法被GC;

append slice 后一直使用,没有进行清理。

var a []int

func test(b []int) {
        a = b[:3]
        return
}

6. 数组的值传递

由于数组时Golang的基本数据类型,每个数组占用不通的内存空间,生命周期互不干扰,很难出现内存泄漏的情况,但是数组作为形参传输时,遵循的时值拷贝,如果函数被多个goroutine调用且数组过大时,则会导致内存使用激增。

//统计nums中target出现的次数
func countTarget(nums [1000000]int, target int) int {
    num := 0
    for i := 0; i < len(nums) && nums[i] == target; i++ {
        num++
    }
    return num
}

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

总结

到此这篇关于golang容易导致内存泄漏的6种情况的文章就介绍到这了,更多相关golang内存泄漏内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 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内存泄漏原因排查详解

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

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

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

  • golang容易导致内存泄漏的6种情况汇总

    目录 1. 定时器使用不当 1.1 time.After()的使用 1.2 time.NewTicker资源未及时释放 2. select阻塞 2.1 导致goroutine阻塞的情况 2.2 循环空转导致CPU暴涨 3. channel阻塞 4. goroutine导致的内存泄漏 4.1 申请过多的goroutine 4.2 goroutine阻塞 4.2.1 I/O问题 4.2.2 互斥锁未释放 4.2.3 死锁 4.2.4 waitgroup使用不当 5. slice 引起的内存泄漏 6.

  • JS造成内存泄漏的几种情况实例分析

    本文实例讲述了JS造成内存泄漏的几种情况.分享给大家供大家参考,具体如下: 介绍: js中的内存垃圾回收机制:垃圾回收器会定期扫描内存,当某个内存中的值被引用为零时就会将其回收.当前变量已经使用完毕但依然被引用,导致垃圾回收器无法回收这就造成了内存泄漏.传统页面每次跳转都会释放内存,所以并不是特别明显. Vue单页面应用中:Web App 与 传统Web的区别,因为Web App是单页面应用页面通过路由跳转不会刷新页面,导致内存泄漏不断堆积,导致页面卡顿. 泄漏点: 1.DOM/BOM 对象泄漏

  • Android 内存泄漏的几种可能总结

    Java是垃圾回收语言的一种,其优点是开发者无需特意管理内存分配,降低了应用由于局部故障(segmentation fault)导致崩溃,同时防止未释放的内存把堆栈(heap)挤爆的可能,所以写出来的代码更为安全. 不幸的是,在Java中仍存在很多容易导致内存泄漏的逻辑可能(logical leak).如果不小心,你的Android应用很容易浪费掉未释放的内存,最终导致内存用光的错误抛出(out-of-memory,OOM). 一般内存泄漏(traditional memory leak)的原因

  • 分析Android内存泄漏的几种可能

    前言 内存泄漏简单地说就是申请了一块内存空间,使用完毕后没有释放掉.它的一般表现方式是程序运行时间越长,占用内存越多,最终用尽全部内存,整个系统崩溃.由程序申请的一块内存,且没有任何一个指针指向它,那么这块内存就泄露了. 从用户使用程序的角度来看,内存泄漏本身不会产生什么危害,作为一般的用户,根本感觉不到内存泄漏的存在.真正有危害的是内存泄漏的堆积,这会最终消耗尽系统所有的内存.从这个角度来说,一次性内存泄漏并没有什么危害,因为它不会堆积,而隐式内存泄漏危害性则非常大,因为较之于常发性和偶发性内

  • Vue优化:常见会导致内存泄漏问题及优化详解

    如果你在用 Vue 开发应用,那么就要当心内存泄漏的问题.这个问题在单页应用 (SPA) 中尤为重要,因为在 SPA 的设计中,用户使用它时是不需要刷新浏览器的,所以 JavaScript 应用需要自行清理组件来确保垃圾回收以预期的方式生效.因此在vue开发过程中,你需要时刻警惕内存泄漏的问题,这些内存泄漏往往会发生在使用 Vue 之外的其它进行 DOM 操作的三方库时,请确保测试应用的内存泄漏问题并在适当的时机做必要的组件清理. 下面是我开发过程中遇到,并查资料总结的内存泄漏问题,会持续更新中

  • 详解Java内存溢出的几种情况

    JVM(Java虚拟机)是一个抽象的计算模型.就如同一台真实的机器,它有自己的指令集和执行引擎,可以在运行时操控内存区域.目的是为构建在其上运行的应用程序提供一个运行环境.JVM可以解读指令代码并与底层进行交互:包括操作系统平台和执行指令并管理资源的硬件体系结构. 1. 前言 JVM提供的内存管理机制和自动垃圾回收极大的解放了用户对于内存的管理,大部分情况下不会出现内存泄漏和内存溢出问题.但是基本不会出现并不等于不会出现,所以掌握Java内存模型原理和学会分析出现的内存溢出或内存泄漏,对于使用J

  • MySQL导致索引失效的几种情况

    目录 一.准备工作 二.索引失效规则 1.优先使用联合索引 2.最左匹配原则 3.范围条件右边的列索引失效 4.计算.函数导致索引失效 5.类型转换导致索引失效 6.不等于(!= 或者<>)索引失效 7.is null可以使用索引,is not null无法使用索引 8.like以%开头,索引失效 9.OR前后存在非索引的列,索引失效 10.字符集不统一 三.建议 一.准备工作 首先准备两张表用于演示: CREATE TABLE `student_info` ( `id` int NOT NU

  • 详解Vue 数据更新了但页面没有更新的 7 种情况汇总及延伸总结

    如果你发现你自己需要在 Vue 中做一次强制更新,99.9% 的情况,是你在某个地方做错了事. 1. Vue 无法检测实例被创建时不存在于 data 中的 property 原因:由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在 data 对象上存在才能让 Vue 将它转换为响应式的. 场景: var vm = new Vue({ data:{}, // 页面不会变化 template: '<div>{{message}

  • MySQL索引失效的几种情况汇总

    一.索引不存储null值 更准确的说,单列索引不存储null值,复合索引不存储全为null的值.索引不能存储Null,所以对这列采用is null条件时,因为索引上根本 没Null值,不能利用到索引,只能全表扫描. 为什么索引列不能存Null值? 将索引列值进行建树,其中必然涉及到诸多的比较操作.Null值的特殊性就在于参与的运算大多取值为null. 这样的话,null值实际上是不能参与进建索引的过程.也就是说,null值不会像其他取值一样出现在索引树的叶子节点上. 二.不适合键值较少的列(重复

  • js内存泄露的几种情况详细探讨

    内存泄露是指一块被分配的内存既不能使用,又不能回收,直到浏览器进程结束.在C++中,因为是手动管理内存,内存泄露是经常出现的事情.而现在流行的C#和Java等语言采用了自动垃圾回收方法管理内存,正常使用的情况下几乎不会发生内存泄露.浏览器中也是采用自动垃圾回收方法管理内存,但由于浏览器垃圾回收方法有bug,会产生内存泄露. 1.当页面中元素被移除或替换时,若元素绑定的事件仍没被移除,在IE中不会作出恰当处理,此时要先手工移除事件,不然会存在内存泄露. 复制代码 代码如下: <div id="

随机推荐