在 Go 語言中,雖然有垃圾收集器(GC)管理內存,但一些編程錯誤仍然可能導致內存泄漏。這些內存泄漏通常不是由于傳統意義上的未釋放內存,而是由于程序不合理的持有內存資源,使得 GC 無法回收不再需要的數據。以下是常見的導致 Go 內存泄漏的場景:
### 1\. **Goroutine 泄漏**
如果 Goroutine 被創建但無法正常退出或被阻塞,可能會一直占用內存資源,造成 Goroutine 泄漏。
#### 場景示例:
~~~
go復制代碼func startGoroutine(ch chan int) {
go func() {
for {
select {
case v := <-ch:
fmt.Println(v)
}
}
}()
}
~~~
* 如果 `ch` 通道關閉后沒有關閉 Goroutine,或者 Goroutine 一直在等待沒有數據到來的通道,那么 Goroutine 會一直運行或阻塞,導致內存無法回收。
**解決方法**:確保 Goroutine 能夠在不需要時退出,通常可以通過關閉通道或者增加超時機制來控制。
~~~
go復制代碼func startGoroutine(ch chan int, done chan struct{}) {
go func() {
for {
select {
case v := <-ch:
fmt.Println(v)
case <-done:
return
}
}
}()
}
~~~
### 2\. **持有大對象的引用**
如果某個數據結構(如切片、映射、結構體)持有大對象的引用,但程序實際上不再需要這些對象的某些部分,GC 將無法回收這部分不需要的數據。
#### 場景示例:
~~~
go復制代碼func processData() {
data := make([]byte, 1000) // 分配了 1000 字節
// 假設我們只需要其中一部分
slice := data[:10] // 只使用了 10 字節
_ = slice
}
~~~
* 雖然只使用了 `data` 的一部分(`slice`),但原始的 `data` 仍然在內存中占據 1000 字節,無法被 GC 回收。
**解決方法**:如果不再需要原始的大對象,可以拷貝需要的數據到新的切片中來釋放多余的內存。
~~~
go復制代碼func processData() {
data := make([]byte, 1000)
slice := make([]byte, 10)
copy(slice, data[:10]) // 只保留需要的數據
}
~~~
### 3\. **全局變量或單例模式**
Go 中的全局變量或長生命周期的對象(如單例模式中的數據)會導致內存長時間無法釋放,因為它們的生命周期與程序一致。只要這些變量沒有被合理地清理,它們所占用的內存就會持續存在。
#### 場景示例:
~~~
go復制代碼var cache = map[string]string{}
func addToCache(key, value string) {
cache[key] = value
}
~~~
* `cache` 是全局變量,如果不及時清理無用的數據,內存將不斷增長,最終可能導致內存泄漏。
**解決方法**:定期清理全局變量中的無用數據,或者為其設置合理的淘汰策略。
### 4\. **切片的容量增長**
在 Go 中,切片的容量可能比實際使用的大小要大得多,特別是在反復擴展切片的過程中。如果不注意控制切片的容量增長,可能會造成內存浪費。
#### 場景示例:
~~~
go復制代碼func appendData() {
data := make([]int, 0, 100)
for i := 0; i < 1000; i++ {
data = append(data, i)
}
}
~~~
* `data` 的容量會隨著不斷 `append` 增長,最終占用的內存可能遠大于需要的內存,而原來的較小容量的切片部分仍然無法被回收。
**解決方法**:在確定不再需要額外容量時,可以使用 `copy` 或 `append` 函數截斷切片,以釋放不必要的容量。
~~~
go復制代碼func appendData() {
data := make([]int, 0, 100)
for i := 0; i < 1000; i++ {
data = append(data, i)
}
data = append([]int(nil), data...) // 重建切片以清理多余的容量
}
~~~
### 5\. **未關閉的通道**
未關閉的通道也可能導致內存泄漏,特別是當通道中的發送或接收 Goroutine 永遠阻塞時,這樣會導致 Goroutine 泄漏和內存積累。
#### 場景示例:
~~~
go復制代碼func sendToChannel(ch chan int) {
for i := 0; i < 10; i++ {
ch <- i
}
// 通道未關閉,可能導致阻塞
}
~~~
**解決方法**:當不再需要時,及時關閉通道,避免內存泄漏和 Goroutine 阻塞。
~~~
go復制代碼func sendToChannel(ch chan int) {
for i := 0; i < 10; i++ {
ch <- i
}
close(ch) // 正確關閉通道
}
~~~
### 6\. **使用不必要的保持引用**
如果某個對象被其他對象持有引用,GC 將無法回收這個對象。常見的情況是某些緩存結構不合理地保留了對大量數據的引用。
#### 場景示例:
~~~
go復制代碼type Node struct {
value int
next *Node
}
func createList() *Node {
head := &Node{value: 1}
current := head
for i := 2; i <= 1000; i++ {
current.next = &Node{value: i}
current = current.next
}
return head
}
~~~
* 如果鏈表結構中的某個節點被持有,即使鏈表已經不再需要,整個鏈表的內存也無法被回收。
**解決方法**:避免不必要的保持引用,及時釋放不再使用的數據結構。
### 7\. **長生命周期的 goroutine 隊列或池**
長生命周期的隊列或者池會導致一些 Goroutine 無法及時釋放,特別是當隊列或池中的任務沒有被及時處理完畢時。
#### 場景示例:
~~~
go復制代碼var taskQueue = make(chan func(), 100)
func worker() {
for task := range taskQueue {
task()
}
}
~~~
* 如果 `taskQueue` 長期未被清空,或者沒有關閉,這些 Goroutine 可能會一直阻塞。
**解決方法**:為任務隊列設定合理的大小,并確保在程序終止時關閉通道或清理任務。
* * *
### 總結
盡管 Go 語言有自動垃圾回收機制,以下情況仍然可能導致內存泄漏:
* Goroutine 泄漏(未正常退出或阻塞)。
* 持有大對象的引用。
* 全局變量或單例模式不及時清理。
* 切片容量增長導致內存浪費。
* 未關閉的通道。
* 使用不必要的保持引用。
* 長生命周期的 Goroutine 隊列或池。
為避免內存泄漏,開發者需要在設計程序時小心管理對象的生命周期、及時釋放資源,并使用工具如 `pprof` 監控內存使用情況。
- 草稿
- Golang
- 切片 slice
- 數組和切片的區別
- 左閉右開
- make([]int, 5) 和 make([]int, 0, 5) 區別
- 切片非線程安全,并發操作為啥不會像map一樣報錯
- []struct{} 如何遍歷
- 切片如何刪除某個元素
- append 一個nil 切片
- 哈希表 map
- 并發操作
- 并發寫報錯
- 并發讀不會報錯
- 并發讀有寫報錯
- 并發迭代有寫報錯
- 自制并發安全字典
- 官方并發安全字典
- 對未初始化的 map 進行賦值操作
- map的底層
- 無序輸出
- 等量擴容
- 實現集合
- map的key可以使哪些值
- 協程 go
- 協程相關閱讀
- 進程、線程、協程
- 協程 (捕獲異常 和 協程池)
- GPM 模型
- CSP模型
- channel
- channel 相關操作
- 交替打印
- 如何讓channel 只能接收/只能發送
- channel 常見報錯
- channel 死鎖
- nil channel 和 已關閉的 channel
- 使用 select 來多路復用 channel
- channel 的使用
- 接口和結構體
- 簡單使用
- 兩個結構體能否比較
- 工廠模式
- 概念
- 簡單工廠
- 方法工廠
- 堆和棧,值類型和引用類型,內存逃逸,垃圾回收
- 棧和堆
- 內存逃逸
- 值類型和引用類型
- 垃圾回收方式
- 性能優化分析工具 pprof
- golang 代碼片段
- 片段一 defer
- 片段二 channel
- Golang 相關
- Golang 相關閱讀
- Golang 1-10
- make 和 new 的區別
- 使用指針的場景
- Go語言的context包
- 位運算
- Copy 是淺拷貝還是深拷貝
- init 函數 和 sync.Once
- select 多路復用
- Golang 其它
- MongoDB
- 可比較類型 與 可轉json 類型
- Gorm
- 面向對象和面向過程
- go語言實現-面向對象
- go語言實現-面向過程
- 限流,熔斷,降級
- 了解
- 熔斷配置
- 熔斷例子
- 服務降級
- github.com/alibaba/sentinel-golang
- 互斥鎖 讀寫鎖 原子鎖
- 為什么需要鎖
- 互斥鎖
- 讀寫鎖
- 原子鎖
- 互斥鎖性能對比
- 原子鎖性能對比
- 互斥鎖 or 原子鎖?
- 條件鎖
- 計數器
- GoFrame
- GF1.16版本
- 修改使用的表
- 按天、周、月、年
- GoFrame 文檔
- 配置文件
- 生成腳本
- 排序算法
- 相關排序
- 冒泡排序
- 選擇排序
- 插入排序
- 快速排序
- 歸并排序
- 堆排序
- 數據庫
- 分布式怎么保證線程安全
- 數據庫實現方式
- 基于表記錄
- 樂觀鎖
- 悲觀鎖
- Redis實現方式
- Zookeeper實現方式
- Mysql 相關
- group_concat
- 索引優化
- 索引優化1
- 定期分析和優化索引
- 覆蓋索引
- 組合索引
- 聚簇索引和非聚簇索引
- 索引類型與方式、聚簇與非聚簇索引
- 事務特征和隔離級別
- 查詢優化
- mysql自增表插入數據時,Id不連續問題
- InnoDB引擎 和 MyISAM引擎區別
- 鎖
- 悲觀鎖和樂觀鎖
- 查詢,更新,插入語句
- 什么是死鎖
- 怎么處理死鎖
- MySQL 隔離級別
- 事務特征
- 隔離級別
- 廢棄3
- 索引
- 索引類型和方式、聚簇和非聚簇索引(上)
- 索引類型和方式、聚簇和非聚簇索引(下)
- 回表、覆蓋索引、最左前綴、聯合索引、索引下推、索引合并
- Mysql 優化
- 索引的原理
- 千萬級表修改表結構
- Redis
- 獲取隨機三條數據
- Redis 持久化方式
- 全量模式 RDB 冷備份(內存快照)
- 增量模式 AOF 熱備份(文件追加)
- 過期key的刪除策略、內存淘汰機制
- 數據結構
- 位圖
- 網絡
- 網絡相關
- 游戲同步方式:幀同步和狀態同步
- Websocket
- OSI模型
- TCP 與 UDP
- 三次握手四次揮手
- Http 狀態碼
- 1xx(信息性狀態碼)
- 101 服務端代碼
- 101 客戶端代碼
- 2xx(成功狀態碼)
- 3xx(重定向狀態碼)
- 302 服務端代碼
- 302 客戶端代碼
- 4xx(客戶端錯誤狀態碼)
- 5xx(服務器錯誤狀態碼)
- 如何排查接口問題
- 網絡請求和響應過程
- time_wait
- keep-alive
- http 和 rpc 的區別
- I/O多路復用 select和poll
- too many open file
- 其它技術
- git 相關操作
- 修改提交備注
- 多個提交合并成一個提交
- 回退版本
- 小程序和公眾號
- 消息模板
- 獲取code
- 靜默登錄
- 其它技術相關
- C盤空間不足
- 生成式人工智能AIGC
- 共享文件
- 接口文檔, mock提供測試數據
- 抓包工具
- Python
- 安裝包失敗
- 自動化測試 Scrapy
- AIGC:人工智能生成內容
- PHP
- xhprof 性能分析
- 一鍵安裝
- 哈希沖突的解決方式
- 鏈地址法(拉鏈法)
- 開放地址法
- 再哈希
- 概念1
- Nginx
- 負載均衡方式
- 加密解密
- 簡單了解
- 簽名算法例子
- 碼例子1
- 代碼例子2
- Linux
- netstat (用于查看和管理網絡連接和路由表)
- ps 用于查看和管理進程
- ab 壓測
- nohup 守護進程
- lsof (List Open File 獲取被進程打開文件的信息)
- tail 查看日志
- 各類linux同步機制
- Socket 服務端的實現,select 和epoll的區別?
- scp 傳輸,awk 是一個強大的文本分析工具
- pidof
- 項目
- 棋牌
- 牌的編碼
- 出牌規則
- 洗牌
- 股票
- 股票知識
- 龍虎榜數據緩存方式
- 單日龍虎榜數據
- 單只股票的歷史上榜
- 遇到的問題
- 浮點數精度問題
- Mysql Sum 精度問題(float, double精度問題)
- 分頁問題(數據重復)
- 工具包
- v3
- common.go
- common_test.go
- customized.go
- customized_test.go
- slice.go
- slice_test.go
- time.go
- time_test.go
- v4
- common.go
- common_test.go
- customized.go
- customized_test.go
- slice.go
- time.go
- time_test.go
- 相關閱讀
- 協程 goroutine
- 通道 channel
- json 和 gob 序列化和反序列化
- redis 有序集合
- mysql22
- 相關閱讀 s
- pyTorch
- defer
- 內存泄漏
- 數據傳輸
- 雜項
- 一提
- gogogoo
- 內容