網站首頁 編程語言 正文
1、字符串截取
func main() { var str0 = "12345678901234567890" str1 := str0[:10] }
以上代碼,會有10字節的內存泄漏,我們知道,str0和str1底層共享內存,只要str1一直活躍,str0?就不會被回收,10字節的內存被使用,剩下的10字節內存就造成了臨時性的內存泄漏,直到str1不再活躍
如果str0足夠大,str1截取足夠小,或者在高并發場景中頻繁使用,那么可想而知,會造成臨時性內存泄漏,對性能產生極大影響。
解決方案1:string to []byte, []byte to string
func main() { var str0 = "12345678901234567890" str1 := string([]byte(str0[:10])) }
將需要截取的部分先轉換成[]byte,再轉換成string,但是這種方式會產生兩個10字節的臨時變量,string轉換[]byte時產生一個10字節臨時變量,[]byte轉換string時產生一個10字節的臨時變量
解決方案2:
func main() { var str0 = "12345678901234567890" str1 := (" " + str0[:10])[1:] }
這種方式仍舊會產生1字節的浪費?
解決方案3:strings.Builder
func main() { var str0 = "12345678901234567890" var builder strings.Builder builder.Grow(10) builder.WriteString(str0[:10]) str1 := builder.String() }
這種方式的缺點就是代碼量過多
解決方案4:strings.Repeat
func main() { var str0 = "12345678901234567890" str1 := strings.Repeat(str0[:10], 1) }
這種方式底層還是用到了strings.Builder,優點就是將方案3進行了封裝,代碼量得到了精簡
2、切片截取引起子切片內存泄漏
func main() { var s0 = []int{1, 2, 3, 4, 5, 6, 7, 8, 9} s1 := s0[:5] }
這種情況與字符串截取引起的內存泄漏情況類似,s1活躍情況下,造成s0中部分內存泄漏
解決方案:append
func main() { var s0 = []int{1, 2, 3, 4, 5, 6, 7, 8, 9} s1 := append(s0[:0:0], s0[:5]...) }
?append為內置函數,go源碼src/builtin/builtin.go中釋義:
// The append built-in function appends elements to the end of a slice. If // it has sufficient capacity, the destination is resliced to accommodate the // new elements. If it does not, a new underlying array will be allocated. // Append returns the updated slice. It is therefore necessary to store the // result of append, often in the variable holding the slice itself: // slice = append(slice, elem1, elem2) // slice = append(slice, anotherSlice...) // As a special case, it is legal to append a string to a byte slice, like this: // slice = append([]byte("hello "), "world"...) func append(slice []Type, elems ...Type) []Type
3、沒有重置丟失的子切片元素中的指針
func main() { var s0 = []*int{new(int), new(int), new(int), new(int), new(int)} s1 := s0[1:3] }
原切片元素為指針類型,原切片被截取后,丟失的子切片元素中的指針元素未被置空,導致內存泄漏
解決方案:元素置空?
func main() { var s0 = []*int{new(int), new(int), new(int), new(int), new(int)} s0[0], s0[3], s0[4] = nil, nil, nil s1 := s0[1:3] }
4、函數數組傳參
Go數組是值類型,賦值和函數傳參都會復制整個數組
func main() { var arrayA = [3]int{1, 2, 3} var arrayB = [3]int{} arrayB = arrayA fmt.Printf("arrayA address: %p, arrayA value: %+v\n", &arrayA, arrayA) fmt.Printf("arrayB address: %p, arrayB value: %+v\n", &arrayB, arrayB) array(arrayA) } func array(array [3]int) { fmt.Printf("array address: %p, array value: %+v\n", &array, array) }
?打印結果:
arrayA address: 0xc0000ae588, arrayA value: [1 2 3]
arrayB address: 0xc0000ae5a0, arrayB value: [1 2 3]
array address: 0xc0000ae5e8, array value: [1 2 3]
可以看到,三條打印的地址都不相同,說明數組是值傳遞的,這會導致什么問題呢?
如果我們在函數傳參的時候用到了數組傳參,且這個數組夠大(我們假設數組大小為100萬,64位機上消耗的內存約為800w字節,即8MB內存),或者該函數短時間內被調用N次,那么可想而知,會消耗大量內存,對性能產生極大的影響,如果短時間內分配大量內存,而又來不及GC,那么就會產生臨時性的內存泄漏,對于高并發場景相當可怕。
解決方案1:采用指針傳遞
func main() { var arrayA = [3]int{1, 2, 3} var arrayB = &arrayA fmt.Printf("arrayA address: %p, arrayA value: %+v\n", &arrayA, arrayA) fmt.Printf("arrayB address: %p, arrayB value: %+v\n", arrayB, *arrayB) arrayP(&arrayA) } func arrayP(array *[3]int) { fmt.Printf("array address: %p, array value: %+v\n", array, *array) }
打印結果:?
arrayA address: 0xc00000e6a8, arrayA value: [1 2 3]
arrayB address: 0xc00000e6a8, arrayB value: [1 2 3]
array address: 0xc00000e6a8, array value: [1 2 3]
可以看到,三條打印的地址相同,說明指針是引用傳遞的?,三個數組指向的都是同一塊內存,就算數組很大,或者函數短時間被調用N次,也不會產生額外的內存開銷,這樣會不會有隱患呢?
有,如果arrayA的指針地址發生變化,那么,arrayB和函數內array的指針地址也隨之改變,稍不注意,容易發生bug
解決方案2:利用切片可以很好的解決以上兩個問題
func main() { var arrayA = [3]int{1, 2, 3} var arrayB = arrayA[:] fmt.Printf("arrayA address: %p, arrayA value: %+v\n", &arrayA, arrayA) fmt.Printf("arrayB address: %p, arrayB value: %+v\n", &arrayB, arrayB) arrayS(arrayB) } func arrayS(array []int) { fmt.Printf("array address: %p, array value: %+v\n", &array, array) }
打印結果:
arrayA address: 0xc00000e6a8, arrayA value: [1 2 3]
arrayB address: 0xc0000040d8, arrayB value: [1 2 3]
array address: 0xc000004108, array value: [1 2 3]
?可以看到,三條打印的地址都不相同,而切片本身是一個引用類型,arrayA和arrayB底層共享內存,不會產生額外內存開銷,而且arrayA的指針地址發生改變,arrayB的指針地址也不會改變,切片的數據結構如下:
type slice struct { array unsafe.Pointer len int cap int }
5、goroutine
“Go里面10次內存泄漏有9次都是goroutine泄漏引起的”
有些編碼不當的情況下,goroutine被長期掛住,導致該協程中的內存也無法被釋放,就會造成永久性的內存泄漏。例如協程結束時協程中的channel沒有關閉,導致一直阻塞;例如協程中有死循環;等等
我們來看下
func main() { ticker := time.NewTicker(time.Second * 1) for { <-ticker.C ch := make(chan int) go func() { for i := 0; i < 100; i++ { ch <- i } }() for v := range ch { if v == 50 { break } } } }
將代碼運行起來,并利用pprof工具,在web輸入http://localhost/debug/pprof/,我們可以看到,goroutine的數量隨著時間在不斷的增加,而且絲毫沒有減少的跡象
?這是因為break的時候,協程中的channel并沒有關閉,導致協程一直存活,無法被回收
解決方案:
func main() { ticker := time.NewTicker(time.Second * 1) for { <-ticker.C cxt, cancel := context.WithCancel(context.Background()) ch := make(chan int) go func(cxt context.Context) { for i := 0; i < 100; i++ { select { case <-cxt.Done(): return case ch <- i: } } }(cxt) for v := range ch { if v == 50 { cancel() break } } } }
利用context,在break之前cancel,目的就是通知協程退出,這樣就避免了goroutine泄漏?
6、定時器
1)time.After
func main() { ch := make(chan int) go func() { for { timerC := time.After(100 * time.Second) //timerC 每次都是重新創建的,什么意思呢?簡單說來,當 select 成功監聽 ch 并進入它的處理分支,下次循環 timerC 重新創建了,時間肯定就重置了。 select { //如果有多個 case 都可以運行,select 會隨機公平選擇出一個執行。其余的則不會執行 case num := <-ch: fmt.Println("get num is", num) case <-timerC: //等價于 case <-time.After(100 * time.Second) fmt.Println("time's up!!!") //done<-true } } }() for i := 1; i < 100000; i++ { ch <- i time.Sleep(time.Millisecond) } }
?以上代碼會造成內存泄漏,time.After底層實現是一個timer,而定時器未到觸發時間,該定時器不會被gc回收,從而導致臨時性的內存泄漏,而如果定時器一直在創建,那么就造成了永久性的內存泄漏了。
解決方案:采用timer定時器
func main() { ch := make(chan int) go func() { timer := time.NewTimer(100 * time.Second) defer timer.Stop() for { timer.Reset(100 * time.Second) select { case num := <-ch: fmt.Println("get num is", num) case <-timer.C: fmt.Println("time's up!!!") } } }() for i := 1; i < 100000; i++ { ch <- i time.Sleep(time.Millisecond) } }
?創建timer定時器,每次需要啟動定時器的時候,使用Reset方法重置定時器,這樣就不用每次都要創建新的定時器了
2)timer、ticker
在高并發、高性能場景中,使用time.NewTimer或者time.NewTicker定時器,都需要注意及時調用Stop方法來及時釋放資源,否則可能造成臨時性或者永久性的內存泄漏。
總結
原文鏈接:https://blog.csdn.net/m0_37290103/article/details/116493163
相關推薦
- 2022-06-21 Flutter實現單選,復選和開關組件的示例代碼_Android
- 2023-01-18 Go語言讀取YAML?配置文件的兩種方式分享_Golang
- 2022-05-25 <C++>深淺拷貝與初始化列表技巧你真的會了嗎
- 2022-09-03 python四則運算表達式求值示例詳解_python
- 2022-07-04 PyG搭建GCN模型實現節點分類GCNConv參數詳解_python
- 2023-03-04 C++容器適配器的概念與示例_C 語言
- 2022-04-12 Cannot read property ‘forEach‘ of undefined
- 2022-12-11 C語言中求解圖形的問題_C 語言
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支