最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

詳解Golang的GC和內(nèi)存逃逸

 更新時(shí)間:2023年07月07日 08:23:33   作者:編程妲己  
這篇文章主要給大家詳細(xì)介紹了Golang的GC和內(nèi)存逃逸,文章中有詳細(xì)的代碼示例,對我們的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下

簡介

每個(gè)版本的Golang的垃圾回收都在不斷優(yōu)化中,而且方法和策略都在變化,因此這里只是總結(jié)出以下幾個(gè)關(guān)鍵點(diǎn):

  • 什么樣的數(shù)據(jù)需要GC
  • 觸發(fā)GC的條件是什么
  • GC時(shí)發(fā)生了什么
  • 能否從代碼層面上提高GC的效率

GC的基本流程

Golang在確定的時(shí)間,或者內(nèi)存分配到達(dá)一定程度時(shí),進(jìn)行GC。GC時(shí),會(huì)停止STW(Stop The World),即對外的服務(wù)都會(huì)暫停,然后進(jìn)行垃圾回收處理。Go1.12引入了三色標(biāo)記法和write-barrier的方式;在Go1.14中,引入看了搶占式回收機(jī)制。

write-barrier機(jī)制:

假設(shè)有4個(gè)G在運(yùn)行,如下圖:

進(jìn)行GC的時(shí)候,需要STW,此時(shí)的4個(gè)G都要停止工作。如果有一個(gè)沒有停止工作的,則GC暫時(shí)不能發(fā)生。比如下圖:

第4個(gè)G沒停止工作,則GC需要等待其結(jié)束。Go1.14中,可以搶占第4個(gè)G的工作狀態(tài),保存其狀態(tài)后,再進(jìn)行GC

GC的時(shí)候,GC機(jī)制會(huì)征用一些G并發(fā)輔助進(jìn)行工作,一般有25%的G會(huì)被征用。

整體工作流程:

  • 創(chuàng)建白、灰和黑三個(gè)集合
  • 初始化所有的待回收對象都是白色的
  • 從根節(jié)點(diǎn)遍歷對象,不遞歸;遍歷到的白色對象放到灰色集合當(dāng)中
  • 之后遍歷灰色集合,把灰色對象引用的對象,從白色集合中放入灰色集合,并把現(xiàn)在的灰色對象放入黑色集合中
  • 重復(fù)上一步,知道灰色集合是空的
  • 通過write-barrier檢測對象的變化,重復(fù)以上操作
  • 回收所有的白色對象

GC回收的對象

永遠(yuǎn)不要過早的優(yōu)化程序?。。?/strong>

棧內(nèi)存分配和回收的代價(jià)遠(yuǎn)遠(yuǎn)小于堆內(nèi)存。Golang的垃圾回收發(fā)生在全局的堆上和每個(gè)Goroutinue的棧上?;厥諚?nèi)存只需要兩個(gè)CPU指令,push和pop。然而,分配在棧內(nèi)存的數(shù)據(jù),需要在編譯期間就得知道type和size。

Golang的編譯器使用“逃逸分析”的方式,決定采取?;厥者€是堆回收的方式。如果發(fā)生逃逸,則使用堆回收。

go build -gcflags '-m'命令可以分析內(nèi)存逃逸的現(xiàn)象。

發(fā)生內(nèi)存逃逸的幾種情況:

  • 向chan中發(fā)送數(shù)據(jù)的指針或者包含指針的值。
  • 因?yàn)榫幾g器此時(shí)不知道值什么時(shí)候會(huì)被接收,因此只能放入堆中
  • 非直接的函數(shù)調(diào)用,比如在閉包中引用包外的值,因?yàn)殚]包執(zhí)行的生命周期可能會(huì)超過函數(shù)周期,因此需要放入堆中
  • 在slice或map中存儲(chǔ)指針或者包含指針的值。
  • 比如[]*string,即使slice是在棧上,其存儲(chǔ)的值仍然會(huì)放入堆中
  • slice如果底層使用array作為容器,在使用append擴(kuò)容的時(shí)候。但是,如果知道具體擴(kuò)容的數(shù)量,則仍然在棧上。
  • 如果在編譯期間,slice知道自己的size,那么放入棧中。更多的時(shí)候,是不知道size的,比如append的時(shí)候,此時(shí)只能放入堆中。
  • interface類型多態(tài)的時(shí)候調(diào)用方法,此時(shí)會(huì)發(fā)生逃逸
  • 指針、slice和map作為參數(shù)返回的時(shí)候,此時(shí)肯定要發(fā)生逃逸。

總結(jié)一下發(fā)生逃逸的結(jié)論:

  • 首先明確一點(diǎn),Golang中所有的數(shù)據(jù)都是按值傳遞,這點(diǎn)和C語言是一樣的(注意Golang中的數(shù)組名是值,和C的差別)。所謂的map、slice和chan等是引用,其本質(zhì)原因是,這些結(jié)構(gòu)的內(nèi)部都有指針,復(fù)制的時(shí)候,內(nèi)部都是復(fù)制的指針,因此表現(xiàn)的是傳值。
  • 在函數(shù)調(diào)用中,對于指針的情況,只要指向的地址的所有者只有一個(gè),那么必然是棧回收;而一旦存在地址存在不確定變化時(shí),則轉(zhuǎn)換成堆的數(shù)據(jù)。比如slice情況,因?yàn)閟lice會(huì)擴(kuò)容或者縮容,因此造成不確定情況。

以下使用代碼示例說明:

package main
func main() {
    ch := make(chan int, 1)
    x := 5
    ch <- x  // x不發(fā)生逃逸,因?yàn)橹皇菑?fù)制的值
    ch1 := make(chan *int, 1)
    y := 5
    py := &y
    ch1 <- py  // y逃逸,因?yàn)榈刂穫魅肓薱han
    z := 5
    pz := &z  // z不逃逸,因?yàn)槭谴_定性析構(gòu)
    *pz += 1
 }

執(zhí)行命令:go build -gcflags ./main.go,得到結(jié)論是y z都發(fā)生了逃逸。

# command-line-arguments
.\main.go:3:6: can inline main as: func() { ch := make(chan int, 1); x := 5; ch <- x; ch1 := make(chan *int, 1); y := 5; py := &y; ch1 <- py; z := 5; pz := &z; *pz += 1 }
.\main.go:9:2: y escapes to heap:
.\main.go:9:2:   flow: py = &y:
.\main.go:9:2:     from &y (address-of) at .\main.go:10:8
.\main.go:9:2:     from py := &y (assign) at .\main.go:10:5
.\main.go:9:2:   flow: {heap} = py:
.\main.go:9:2:     from ch1 <- py (send) at .\main.go:11:6
.\main.go:9:2: moved to heap: y

如果使用slice和map的模式:

package main
func main() {
    var x int
    x = 10
    var ls []*int
    ls = append(ls, &x)
    var y int
    var mp map[string]*int
    mp["y"] = &y
}

結(jié)論分析:

# command-line-arguments
.\main.go:3:6: can inline main as: func() { var x int; x = <N>; x = 10; var ls []*int; ls = <N>; ls = append(ls, &x); var y int; y = <N>; var mp map[string]*int; mp = <N>; mp["y"] = &y }
.\main.go:4:6: x escapes to heap:
.\main.go:4:6:   flow: {heap} = &x:
.\main.go:4:6:     from &x (address-of) at .\main.go:7:18
.\main.go:4:6:     from append(ls, &x) (call parameter) at .\main.go:7:13
.\main.go:9:6: y escapes to heap:
.\main.go:9:6:   flow: {heap} = &y:
.\main.go:9:6:     from &y (address-of) at .\main.go:11:12
.\main.go:9:6:     from mp["y"] = &y (assign) at .\main.go:11:10
.\main.go:4:6: moved to heap: x
.\main.go:9:6: moved to heap: y

使用閉包捕獲指針的模式:

package main
import "time"
func main() {
    x := 10
    go func(x *int) {
        *x += 1
    }(&x) // 捕獲的瞬間,x沒有移動(dòng)到heap上,但是整個(gè)閉包移動(dòng)到了heap上,因此x也跟隨閉包被移動(dòng)到heap上了
    time.Sleep(time.Second * 2)
}

結(jié)論分析:

# command-line-arguments
.\main.go:5:6: cannot inline main: unhandled op GO
.\main.go:7:5: can inline main.func1 as: func(*int) { *x += 1 }
.\main.go:7:5: func literal escapes to heap:
.\main.go:7:5:   flow: {heap} = &{storage for func literal}:
.\main.go:7:5:     from func literal (spill) at .\main.go:7:5
.\main.go:7:5:     from go (func literal)(&x) (call parameter) at .\main.go:7:2
.\main.go:6:2: x escapes to heap:
.\main.go:6:2:   flow: {heap} = &x:
.\main.go:6:2:     from &x (address-of) at .\main.go:9:4
.\main.go:6:2:     from go (func literal)(&x) (call parameter) at .\main.go:7:2
.\main.go:7:10: x does not escape
.\main.go:6:2: moved to heap: x
.\main.go:7:5: func literal escapes to heap

對于slice擴(kuò)容的情況:

package main
import (
    "os"
    "strconv"
)
func main() {
    ls := []int{1, 2, 3}
    ls = append(ls, 4) // 確定性的,不逃逸,編譯期間可以知道
    var n int
    n, _ = strconv.Atoi(os.Args[1])  // 輸入數(shù)據(jù)后,則結(jié)果不可知,因此可能逃逸
    ls1 := []int{1, 2, 3}
    for i := 0; i < n; i++ {
        ls1 = append(ls1, 1)
    }
}

interface類型的GC,涉及使用interface類型轉(zhuǎn)換并調(diào)用對應(yīng)的方法的時(shí)候,都會(huì)發(fā)生內(nèi)存逃逸,給出代碼示例:

package main
type foo interface {
    fooFunc()
}
type foo1 struct{}
func (f1 foo1) fooFunc() {}
type foo2 struct{}
func (f2 *foo2) fooFunc() {}
func main() {
    var f foo
    f = foo1{}
    f.fooFunc()   // 調(diào)用方法時(shí),發(fā)生逃逸,因?yàn)榉椒ㄊ莿?dòng)態(tài)分配的
    f = &foo2{}
    f.fooFunc()
}

執(zhí)行說明:

go-code ? go build -gcflags "-m" main.go                                                                                                 
# command-line-arguments
.\main.go:9:6: can inline foo1.fooFunc
.\main.go:13:6: can inline (*foo2).fooFunc
.\main.go:13:7: f2 does not escape
.\main.go:17:4: foo1 literal escapes to heap
.\main.go:19:6: &foo2 literal escapes to heap
<autogenerated>:1: leaking param: .this
<autogenerated>:1: inlining call to foo1.fooFunc
<autogenerated>:1: .this does not escape

返回slice等的情況:

package main
func foo() []int {
	return []int{1, 2, 3}
}
func main() {
	ls := foo() // 發(fā)生逃逸
	ls = append(ls, 1)
}

分析結(jié)果:

# command-line-arguments
.\main.go:3:6: can inline foo as: func() []int { return []int literal }
.\main.go:7:6: can inline main as: func() { ls := foo(); ls = append(ls, 1) }
.\main.go:8:11: inlining call to foo func() []int { return []int literal }
.\main.go:4:14: []int literal escapes to heap:
.\main.go:4:14:   flow: ~r0 = &{storage for []int literal}:
.\main.go:4:14:     from []int literal (spill) at .\main.go:4:14
.\main.go:4:14:     from return []int literal (return) at .\main.go:4:2
.\main.go:4:14: []int literal escapes to heap
.\main.go:8:11: []int literal does not escape

傳值還是傳指針的問題:

根據(jù)上面的分析,指針更容易出現(xiàn)內(nèi)存逃逸的現(xiàn)象。而一旦發(fā)生了內(nèi)存逃逸,則不可避免地對GC造成潛在的壓力。有種錯(cuò)誤的觀念:傳指針的代價(jià)總是比傳值的拷貝代價(jià)小。這種觀念只在像C語言這種沒有GC的低級(jí)語言中可能適用。原因如下:

  • 對指針解引用的時(shí)候,編譯器會(huì)進(jìn)行一些檢查。
  • 指針一般都不是臨近地址的引用,而復(fù)制時(shí),一般都是CPU cash中的數(shù)據(jù),cash line內(nèi)的數(shù)據(jù)的復(fù)制,速度基本和一個(gè)復(fù)制指針相等

因此,對于小型的數(shù)據(jù),一般傳值就夠了。在某些情況下,需要對代碼做一些重構(gòu),以消除成員變量中不必要的指針類型。slice有些情況下,可能也會(huì)造成內(nèi)存逃逸,使用已知固定長度的slice,某些情況下會(huì)減少內(nèi)存逃逸。nterface調(diào)用方法會(huì)發(fā)生內(nèi)存逃逸,某些熱點(diǎn)的情況下,可以考慮優(yōu)化interface的情況。

幾個(gè)總結(jié)

  • 永遠(yuǎn)不要過早的優(yōu)化,使用數(shù)據(jù)驅(qū)動(dòng)優(yōu)化代碼
  • ?;厥盏拇鷥r(jià)遠(yuǎn)遠(yuǎn)小于堆回收的代價(jià)
  • 指針一般情況下會(huì)影響?;厥?/li>
  • 在熱點(diǎn)代碼片段,謹(jǐn)慎的使用interface

以上就是詳解Golang的GC和內(nèi)存逃逸的詳細(xì)內(nèi)容,更多關(guān)于Golang GC和內(nèi)存逃逸的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • go使用consul實(shí)現(xiàn)服務(wù)發(fā)現(xiàn)及配置共享實(shí)現(xiàn)詳解

    go使用consul實(shí)現(xiàn)服務(wù)發(fā)現(xiàn)及配置共享實(shí)現(xiàn)詳解

    這篇文章主要為大家介紹了go使用consul實(shí)現(xiàn)服務(wù)發(fā)現(xiàn)及配置共享實(shí)現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-05-05
  • Go語言中占位符格式化符號(hào)的實(shí)現(xiàn)

    Go語言中占位符格式化符號(hào)的實(shí)現(xiàn)

    本文介紹了Go語言中常用格式化占位符,包括結(jié)構(gòu)體、布爾、整數(shù)、浮點(diǎn)數(shù)、字符串、切片及指針類型,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2025-10-10
  • golang類型轉(zhuǎn)換組件Cast的使用詳解

    golang類型轉(zhuǎn)換組件Cast的使用詳解

    這篇文章主要介紹了golang類型轉(zhuǎn)換組件Cast的使用詳解,幫助大家更好的理解和學(xué)習(xí)使用golang,感興趣的朋友可以了解下
    2021-02-02
  • golang中接口對象的轉(zhuǎn)型兩種方式

    golang中接口對象的轉(zhuǎn)型兩種方式

    這篇文章主要介紹了golang中接口對象的轉(zhuǎn)型方式,大家都知道接口對象的轉(zhuǎn)型有兩種方式,文中通過示例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2021-10-10
  • Go string 字符串處理與格式化詳解從底層原理到工程實(shí)踐

    Go string 字符串處理與格式化詳解從底層原理到工程實(shí)踐

    這篇文章給大家介紹Go string字符串處理與格式化詳解從底層原理到工程實(shí)踐,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧
    2026-05-05
  • Go語言使用sync.Mutex實(shí)現(xiàn)資源加鎖

    Go語言使用sync.Mutex實(shí)現(xiàn)資源加鎖

    數(shù)據(jù)共享是一把雙刃劍,Go語言為我們提供了?sync.Mutex,一種最基礎(chǔ)也是最常用的加鎖方式,用于保證在任意時(shí)刻只有一個(gè) goroutine 能訪問共享資源,下面我們就來看看具體實(shí)現(xiàn)吧
    2025-08-08
  • 使用Go語言實(shí)現(xiàn)一個(gè)簡單的詞頻分析系統(tǒng)

    使用Go語言實(shí)現(xiàn)一個(gè)簡單的詞頻分析系統(tǒng)

    在數(shù)據(jù)分析和文本挖掘中,詞頻統(tǒng)計(jì)(Word Frequency Analysis) 是最基礎(chǔ)也是最常用的技術(shù)之一,本文將帶你用 Go 語言實(shí)現(xiàn)一個(gè)簡易的 詞頻分析系統(tǒng),感興趣的小伙伴可以了解下
    2025-09-09
  • Go工具鏈之go tool fix用法詳解

    Go工具鏈之go tool fix用法詳解

    go tool fix 是 Go 工具鏈中的一個(gè)命令,作用是把指定 Go 程序代碼包中的的所有舊版本代碼修正為新版本的代碼,本文將簡單介紹一下go tool fix的使用方法,感興趣的小伙伴可以參考閱讀下
    2023-07-07
  • Golang請求fasthttp實(shí)踐

    Golang請求fasthttp實(shí)踐

    本文主要介紹了Golang請求fasthttp實(shí)踐,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-11-11
  • Go語言 go程釋放操作(退出/銷毀)

    Go語言 go程釋放操作(退出/銷毀)

    這篇文章主要介紹了Go語言 go程釋放操作(退出/銷毀),具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-04-04

最新評論

泾川县| 宁河县| 疏附县| 石林| 当涂县| 亚东县| 元谋县| 旬阳县| 根河市| 桐乡市| 汉川市| 洛川县| 呼玛县| 郧西县| 阳山县| 清流县| 连江县| 郁南县| 云安县| 杭州市| 娄底市| 永川市| 岳池县| 邵阳市| 奈曼旗| 乌鲁木齐县| 拉孜县| 宁津县| 长寿区| 禄劝| 百色市| 陆丰市| 永善县| 竹山县| 平昌县| 仙桃市| 阳城县| 平顶山市| 如皋市| 友谊县| 重庆市|