真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

關(guān)于GolangGC垃圾回收機(jī)制的詳解

下面由golang教程欄目給大家介紹關(guān)于Golang GC 垃圾回收機(jī)制的詳解,希望對(duì)需要的朋友有所幫助!

10余年的鳳岡網(wǎng)站建設(shè)經(jīng)驗(yàn),針對(duì)設(shè)計(jì)、前端、開發(fā)、售后、文案、推廣等六對(duì)一服務(wù),響應(yīng)快,48小時(shí)及時(shí)工作處理。成都營(yíng)銷網(wǎng)站建設(shè)的優(yōu)勢(shì)是能夠根據(jù)用戶設(shè)備顯示端的尺寸不同,自動(dòng)調(diào)整鳳岡建站的顯示方式,使網(wǎng)站能夠適用不同顯示終端,在瀏覽器中調(diào)整網(wǎng)站的寬度,無(wú)論在任何一種瀏覽器上瀏覽網(wǎng)站,都能展現(xiàn)優(yōu)雅布局與設(shè)計(jì),從而大程度地提升瀏覽體驗(yàn)。創(chuàng)新互聯(lián)從事“鳳岡網(wǎng)站設(shè)計(jì)”,“鳳岡網(wǎng)站推廣”以來,每個(gè)客戶項(xiàng)目都認(rèn)真落實(shí)執(zhí)行。

摘要

在實(shí)際使用 go 語(yǔ)言的過程中,碰到了一些看似奇怪的內(nèi)存占用現(xiàn)象,于是決定對(duì)go語(yǔ)言的垃圾回收模型進(jìn)行一些研究。本文對(duì)研究的結(jié)果進(jìn)行一下總結(jié)。

什么是垃圾回收?

曾幾何時(shí),內(nèi)存管理是程序員開發(fā)應(yīng)用的一大難題。傳統(tǒng)的系統(tǒng)級(jí)編程語(yǔ)言(主要指C/C++)中,程序員必須對(duì)內(nèi)存小心的進(jìn)行管理操作,控制內(nèi)存的申請(qǐng)及釋放。稍有不慎,就可能產(chǎn)生內(nèi)存泄露問題,這種問題不易發(fā)現(xiàn)并且難以定位,一直成為困擾開發(fā)者的噩夢(mèng)。如何解決這個(gè)頭疼的問題呢?過去一般采用兩種辦法:

內(nèi)存泄露檢測(cè)工具。這種工具的原理一般是靜態(tài)代碼掃描,通過掃描程序檢測(cè)可能出現(xiàn)內(nèi)存泄露的代碼段。然而檢測(cè)工具難免有疏漏和不足,只能起到輔助作用。智能指針。這是 c++ 中引入的自動(dòng)內(nèi)存管理方法,通過擁有自動(dòng)內(nèi)存管理功能的指針對(duì)象來引用對(duì)象,是程序員不用太關(guān)注內(nèi)存的釋放,而達(dá)到內(nèi)存自動(dòng)釋放的目的。這種方法是采用最廣泛的做法,但是對(duì)程序員有一定的學(xué)習(xí)成本(并非語(yǔ)言層面的原生支持),而且一旦有忘記使用的場(chǎng)景依然無(wú)法避免內(nèi)存泄露。

為了解決這個(gè)問題,后來開發(fā)出來的幾乎所有新語(yǔ)言(java,python,php等等)都引入了語(yǔ)言層面的自動(dòng)內(nèi)存管理 – 也就是語(yǔ)言的使用者只用關(guān)注內(nèi)存的申請(qǐng)而不必關(guān)心內(nèi)存的釋放,內(nèi)存釋放由虛擬機(jī)(virtual machine)或運(yùn)行時(shí)(runtime)來自動(dòng)進(jìn)行管理。而這種對(duì)不再使用的內(nèi)存資源進(jìn)行自動(dòng)回收的行為就被稱為垃圾回收。

常見的垃圾回收方法引用計(jì)數(shù)(reference counting)

這是最簡(jiǎn)單的一種垃圾回收算法,和之前提到的智能指針異曲同工。對(duì)每個(gè)對(duì)象維護(hù)一個(gè)引用計(jì)數(shù),當(dāng)引用該對(duì)象的對(duì)象被銷毀或更新時(shí)被引用對(duì)象的引用計(jì)數(shù)自動(dòng)減一,當(dāng)被引用對(duì)象被創(chuàng)建或被賦值給其他對(duì)象時(shí)引用計(jì)數(shù)自動(dòng)加一。當(dāng)引用計(jì)數(shù)為0時(shí)則立即回收對(duì)象。

這種方法的優(yōu)點(diǎn)是實(shí)現(xiàn)簡(jiǎn)單,并且內(nèi)存的回收很及時(shí)。這種算法在內(nèi)存比較緊張和實(shí)時(shí)性比較高的系統(tǒng)中使用的比較廣泛,如ios cocoa框架,php,python等。簡(jiǎn)單引用計(jì)數(shù)算法也有明顯的缺點(diǎn):

頻繁更新引用計(jì)數(shù)降低了性能。一種簡(jiǎn)單的解決方法就是編譯器將相鄰的引用計(jì)數(shù)更新操作合并到一次更新;還有一種方法是針對(duì)頻繁發(fā)生的臨時(shí)變量引用不進(jìn)行計(jì)數(shù),而是在引用達(dá)到0時(shí)通過掃描堆棧確認(rèn)是否還有臨時(shí)對(duì)象引用而決定是否釋放。等等還有很多其他方法,具體可以參考這里。循環(huán)引用問題。當(dāng)對(duì)象間發(fā)生循環(huán)引用時(shí)引用鏈中的對(duì)象都無(wú)法得到釋放。最明顯的解決辦法是避免產(chǎn)生循環(huán)引用,如cocoa引入了strong指針和weak指針兩種指針類型?;蛘呦到y(tǒng)檢測(cè)循環(huán)引用并主動(dòng)打破循環(huán)鏈。當(dāng)然這也增加了垃圾回收的復(fù)雜度。標(biāo)記-清除(mark and sweep)

該方法分為兩步,標(biāo)記從根變量開始迭代得遍歷所有被引用的對(duì)象,對(duì)能夠通過應(yīng)用遍歷訪問到的對(duì)象都進(jìn)行標(biāo)記為“被引用”;標(biāo)記完成后進(jìn)行清除操作,對(duì)沒有標(biāo)記過的內(nèi)存進(jìn)行回收(回收同時(shí)可能伴有碎片整理操作)。這種方法解決了引用計(jì)數(shù)的不足,但是也有比較明顯的問題:每次啟動(dòng)垃圾回收都會(huì)暫停當(dāng)前所有的正常代碼執(zhí)行,回收是系統(tǒng)響應(yīng)能力大大降低!當(dāng)然后續(xù)也出現(xiàn)了很多mark&sweep算法的變種(如三色標(biāo)記法)優(yōu)化了這個(gè)問題。

分代收集(generation)

經(jīng)過大量實(shí)際觀察得知,在面向?qū)ο缶幊陶Z(yǔ)言中,絕大多數(shù)對(duì)象的生命周期都非常短。分代收集的基本思想是,將堆劃分為兩個(gè)或多個(gè)稱為 代(generation)的空間。新創(chuàng)建的對(duì)象存放在稱為 新生代(young generation)中(一般來說,新生代的大小會(huì)比 老年代小很多),隨著垃圾回收的重復(fù)執(zhí)行,生命周期較長(zhǎng)的對(duì)象會(huì)被 提升(promotion)到老年代中。因此,新生代垃圾回收和老年代垃圾回收兩種不同的垃圾回收方式應(yīng)運(yùn)而生,分別用于對(duì)各自空間中的對(duì)象執(zhí)行垃圾回收。新生代垃圾回收的速度非???,比老年代快幾個(gè)數(shù)量級(jí),即使新生代垃圾回收的頻率更高,執(zhí)行效率也仍然比老年代垃圾回收強(qiáng),這是因?yàn)榇蠖鄶?shù)對(duì)象的生命周期都很短,根本無(wú)需提升到老年代。

GO的垃圾回收器

go語(yǔ)言垃圾回收總體采用的是經(jīng)典的mark and sweep算法。

1.3版本以前,golang的垃圾回收算法都非常簡(jiǎn)陋,然后其性能也廣被詬病:go runtime在一定條件下(內(nèi)存超過閾值或定期如2min),暫停所有任務(wù)的執(zhí)行,進(jìn)行mark&sweep操作,操作完成后啟動(dòng)所有任務(wù)的執(zhí)行。在內(nèi)存使用較多的場(chǎng)景下,go程序在進(jìn)行垃圾回收時(shí)會(huì)發(fā)生非常明顯的卡頓現(xiàn)象(Stop The World)。在對(duì)響應(yīng)速度要求較高的后臺(tái)服務(wù)進(jìn)程中,這種延遲簡(jiǎn)直是不能忍受的!這個(gè)時(shí)期國(guó)內(nèi)外很多在生產(chǎn)環(huán)境實(shí)踐go語(yǔ)言的團(tuán)隊(duì)都或多或少踩過gc的坑。當(dāng)時(shí)解決這個(gè)問題比較常用的方法是盡快控制自動(dòng)分配內(nèi)存的內(nèi)存數(shù)量以減少gc負(fù)荷,同時(shí)采用手動(dòng)管理內(nèi)存的方法處理需要大量及高頻分配內(nèi)存的場(chǎng)景。1.3版本開始go team開始對(duì)gc性能進(jìn)行持續(xù)的改進(jìn)和優(yōu)化,每個(gè)新版本的go發(fā)布時(shí)gc改進(jìn)都成為大家備受關(guān)注的要點(diǎn)。1.3版本中,go runtime分離了mark和sweep操作,和以前一樣,也是先暫停所有任務(wù)執(zhí)行并啟動(dòng)mark,mark完成后馬上就重新啟動(dòng)被暫停的任務(wù)了,而是讓sweep任務(wù)和普通協(xié)程任務(wù)一樣并行的和其他任務(wù)一起執(zhí)行。如果運(yùn)行在多核處理器上,go會(huì)試圖將gc任務(wù)放到單獨(dú)的核心上運(yùn)行而盡量不影響業(yè)務(wù)代碼的執(zhí)行。go team自己的說法是減少了50%-70%的暫停時(shí)間。1.4版本(當(dāng)前最新穩(wěn)定版本)對(duì)gc的性能改動(dòng)并不多。1.4版本中runtime很多代碼取代了原生c語(yǔ)言實(shí)現(xiàn)而采用了go語(yǔ)言實(shí)現(xiàn),對(duì)gc帶來的一大改變是可以是實(shí)現(xiàn)精確的gc。c語(yǔ)言實(shí)現(xiàn)在gc時(shí)無(wú)法獲取到內(nèi)存的對(duì)象信息,因此無(wú)法準(zhǔn)確區(qū)分普通變量和指針,只能將普通變量當(dāng)做指針,如果碰巧這個(gè)普通變量指向的空間有其他對(duì)象,那這個(gè)對(duì)象就不會(huì)被回收。而go語(yǔ)言實(shí)現(xiàn)是完全知道對(duì)象的類型信息,在標(biāo)記時(shí)只會(huì)遍歷指針指向的對(duì)象,這樣就避免了C實(shí)現(xiàn)時(shí)的堆內(nèi)存浪費(fèi)(解決約10-30%)。1.5版本go team對(duì)gc又進(jìn)行了比較大的改進(jìn)(1.4中已經(jīng)埋下伏筆如write barrier的引入),官方的主要目標(biāo)是減少延遲。go 1.5正在實(shí)現(xiàn)的垃圾回收器是“非分代的、非移動(dòng)的、并發(fā)的、三色的標(biāo)記清除垃圾收集器”。分代算法上文已經(jīng)提及,是一種比較好的垃圾回收管理策略,然1.5版本中并未考慮實(shí)現(xiàn);我猜測(cè)的原因是步子不能邁太大,得逐步改進(jìn),go官方也表示會(huì)在1.6版本的gc優(yōu)化中考慮。同時(shí)引入了上文介紹的三色標(biāo)記法,這種方法的mark操作是可以漸進(jìn)執(zhí)行的而不需每次都掃描整個(gè)內(nèi)存空間,可以減少stop the world的時(shí)間。 由此可以看到,一路走來直到1.5版本,go的垃圾回收性能也是一直在提升,但是相對(duì)成熟的垃圾回收系統(tǒng)(如java jvm和javascript v8),go需要優(yōu)化的路徑還很長(zhǎng)(但是相信未來一定是美好的~)。實(shí)踐經(jīng)驗(yàn)

團(tuán)隊(duì)在實(shí)踐go語(yǔ)言時(shí)同樣碰到最多和最棘手的問題也是內(nèi)存問題(其中g(shù)c為主),這里把遇到的問題和經(jīng)驗(yàn)總結(jié)下,歡迎大家一起交流探討。

go程序內(nèi)存占用大的問題

這個(gè)問題在我們對(duì)后臺(tái)服務(wù)進(jìn)行壓力測(cè)試時(shí)發(fā)現(xiàn),我們模擬大量的用戶請(qǐng)求訪問后臺(tái)服務(wù),這時(shí)各服務(wù)模塊能觀察到明顯的內(nèi)存占用上升。但是當(dāng)停止壓測(cè)時(shí),內(nèi)存占用并未發(fā)生明顯的下降?;撕荛L(zhǎng)時(shí)間定位問題,使用gprof等各種方法,依然沒有發(fā)現(xiàn)原因。最后發(fā)現(xiàn)原來這時(shí)正常的…主要的原因有兩個(gè),

一是go的垃圾回收有個(gè)觸發(fā)閾值,這個(gè)閾值會(huì)隨著每次內(nèi)存使用變大而逐漸增大(如初始閾值是10MB則下一次就是20MB,再下一次就成為了40MB…),如果長(zhǎng)時(shí)間沒有觸發(fā)gc go會(huì)主動(dòng)觸發(fā)一次(2min)。高峰時(shí)內(nèi)存使用量上去后,除非持續(xù)申請(qǐng)內(nèi)存,靠閾值觸發(fā)gc已經(jīng)基本不可能,而是要等最多2min主動(dòng)gc開始才能觸發(fā)gc。

第二個(gè)原因是go語(yǔ)言在向系統(tǒng)交還內(nèi)存時(shí)只是告訴系統(tǒng)這些內(nèi)存不需要使用了,可以回收;同時(shí)操作系統(tǒng)會(huì)采取“拖延癥”策略,并不是立即回收,而是等到系統(tǒng)內(nèi)存緊張時(shí)才會(huì)開始回收這樣該程序又重新申請(qǐng)內(nèi)存時(shí)就可以獲得極快的分配速度。

gc時(shí)間長(zhǎng)的問題

對(duì)于對(duì)用戶響應(yīng)事件有要求的后端程序,golang gc時(shí)的stop the world兼職是噩夢(mèng)。根據(jù)上文的介紹,1.5版本的go再完成上述改進(jìn)后應(yīng)該gc性能會(huì)提升不少,但是所有的垃圾回收型語(yǔ)言都難免在gc時(shí)面臨性能下降,對(duì)此我們對(duì)于應(yīng)該盡量避免頻繁創(chuàng)建臨時(shí)堆對(duì)象(如&abc{}, new, make等)以減少垃圾收集時(shí)的掃描時(shí)間,對(duì)于需要頻繁使用的臨時(shí)對(duì)象考慮直接通過數(shù)組緩存進(jìn)行重用;很多人采用cgo的方法自己管理內(nèi)存而繞開垃圾收集,這種方法除非迫不得已個(gè)人是不推薦的(容易造成不可預(yù)知的問題),當(dāng)然迫不得已的情況下還是可以考慮的,這招帶來的效果還是很明顯的~

goroutine泄露的問題

我們的一個(gè)服務(wù)需要處理很多長(zhǎng)連接請(qǐng)求,實(shí)現(xiàn)時(shí),對(duì)于每個(gè)長(zhǎng)連接請(qǐng)求各開了一個(gè)讀取和寫入?yún)f(xié)程,全部采用endless for loop不停地處理收發(fā)數(shù)據(jù)。當(dāng)連接被遠(yuǎn)端關(guān)閉后,如果不對(duì)這兩個(gè)協(xié)程做處理,他們依然會(huì)一直運(yùn)行,并且占用的channel也不會(huì)被釋放…這里就必須十分注意,在不使用協(xié)程后一定要把他依賴的channel close并通過再協(xié)程中判斷channel是否關(guān)閉以保證其退出。

Golang-gc基本知識(shí)

APR 30TH, 2016 8:02 PM | COMMENTS

這一部分主要介紹golang gc的一些入門的相關(guān)知識(shí),由于gc內(nèi)容涉及比較多,一點(diǎn)一點(diǎn)慢慢整理。

Golang GC的背景golang是基于garbage collection的語(yǔ)言,這是它的設(shè)計(jì)原則。作為一個(gè)有垃圾回收器的語(yǔ)言,gc與程序交互時(shí)候的效率會(huì)影響到整個(gè)程序的運(yùn)行效率。通常程序本身的內(nèi)存管理會(huì)影響gc和程序之間的效率,甚至造成性能瓶頸。Golang GC的相關(guān)問題

主要參的這個(gè):

http://morsmachine.dk/machine-gc

是14年寫的,估計(jì)那個(gè)時(shí)候的gc機(jī)制還比較simple,新版本的golang對(duì)gc的改動(dòng)應(yīng)該會(huì)比較大

還有那個(gè)go語(yǔ)言讀書筆記中關(guān)于golang gc 的相關(guān)部分

關(guān)于內(nèi)存泄露

“內(nèi)存泄露”(Memory Leak)這個(gè)詞看似自己很熟悉,可實(shí)際上卻也從沒有看過它的準(zhǔn)確含義。

內(nèi)存泄露,是從操作系統(tǒng)的角度上來闡述的,形象的比喻就是“操作系統(tǒng)可提供給所有進(jìn)程的存儲(chǔ)空間(虛擬內(nèi)存空間)正在被某個(gè)進(jìn)程榨干”,導(dǎo)致的原因就是程序在運(yùn)行的時(shí)候,會(huì)不斷地動(dòng)態(tài)開辟的存儲(chǔ)空間,這些存儲(chǔ)空間在在運(yùn)行結(jié)束之后后并沒有被及時(shí)釋放掉。應(yīng)用程序在分配了某段內(nèi)存之后,由于設(shè)計(jì)的錯(cuò)誤,會(huì)導(dǎo)致程序失去了對(duì)該段內(nèi)存的控制,造成了內(nèi)存空間的浪費(fèi)。

如果程序在內(nèi)存空間內(nèi)申請(qǐng)了一塊內(nèi)存,之后程序運(yùn)行結(jié)束之后,沒有把這塊內(nèi)存空間釋放掉,而且對(duì)應(yīng)的程序又沒有很好的gc機(jī)制去對(duì)程序申請(qǐng)的空間進(jìn)行回收,這樣就會(huì)導(dǎo)致內(nèi)存泄露。

從用戶的角度來說,內(nèi)存泄露本身不會(huì)有什么危害,因?yàn)檫@不是對(duì)用戶功能的影響,但是“內(nèi)存泄露”如果進(jìn)

對(duì)于 C 和 C++ 這種沒有 Garbage Collection 的語(yǔ)言來講,我們主要關(guān)注兩種類型的內(nèi)存泄漏:

堆內(nèi)存泄漏(Heap leak)。對(duì)內(nèi)存指的是程序運(yùn)行中根據(jù)需要分配通過 malloc,realloc new 等從堆中分配的一塊內(nèi)存,再是完成后必須通過調(diào)用對(duì)應(yīng)的 free 或者 delete 刪掉。如果程序的設(shè)計(jì)的錯(cuò)誤導(dǎo)致這部分內(nèi)存沒有被釋放,那么此后這塊內(nèi)存將不會(huì)被使用,就會(huì)產(chǎn)生 Heap Leak.系統(tǒng)資源泄露(Resource Leak).主要指程序使用系統(tǒng)分配的資源比如 Bitmap,handle ,SOCKET 等沒有使用相應(yīng)的函數(shù)釋放掉,導(dǎo)致系統(tǒng)資源的浪費(fèi),嚴(yán)重可導(dǎo)致系統(tǒng)效能降低,系統(tǒng)運(yùn)行不穩(wěn)定。

內(nèi)存泄露涉及到的相關(guān)問題還有很多,這里暫不展開討論。

常見的 GC 模式

具體的優(yōu)缺點(diǎn)可以參考這個(gè),這里只是進(jìn)行大致介紹。

引用計(jì)數(shù)(reference counting)每個(gè)對(duì)象維護(hù)一個(gè)引用計(jì)數(shù)器,當(dāng)引用該對(duì)象的對(duì)象被銷毀或者更新的時(shí)候,被引用對(duì)象的引用計(jì)數(shù)器自動(dòng)減 1,當(dāng)被應(yīng)用的對(duì)象被創(chuàng)建,或者賦值給其他對(duì)象時(shí),引用 +1,引用為 0 的時(shí)候回收,思路簡(jiǎn)單,但是頻繁更新引用計(jì)數(shù)器降低性能,存在循環(huán)以引用(php,Python所使用的)標(biāo)記清除(mark and sweep)就是 golang 所使用的,從根變量來時(shí)遍歷所有被引用對(duì)象,標(biāo)記之后進(jìn)行清除操作,對(duì)未標(biāo)記對(duì)象進(jìn)行回收,缺點(diǎn):每次垃圾回收的時(shí)候都會(huì)暫停所有的正常運(yùn)行的代碼,系統(tǒng)的響應(yīng)能力會(huì)大大降低,各種 mark&swamp 變種(三色標(biāo)記法),緩解性能問題。分代搜集(generation)jvm 就使用的分代回收的思路。在面向?qū)ο缶幊陶Z(yǔ)言中,絕大多數(shù)對(duì)象的生命周期都非常短。分代收集的基本思想是,將堆劃分為兩個(gè)或多個(gè)稱為代(generation)的空間。新創(chuàng)建的對(duì)象存放在稱為新生代(young generation)中(一般來說,新生代的大小會(huì)比 老年代小很多),隨著垃圾回收的重復(fù)執(zhí)行,生命周期較長(zhǎng)的對(duì)象會(huì)被提升(promotion)到老年代中(這里用到了一個(gè)分類的思路,這個(gè)是也是科學(xué)思考的一個(gè)基本思路)。

因此,新生代垃圾回收和老年代垃圾回收兩種不同的垃圾回收方式應(yīng)運(yùn)而生(先分類,之后再對(duì)癥下藥),分別用于對(duì)各自空間中的對(duì)象執(zhí)行垃圾回收。新生代垃圾回收的速度非常快,比老年代快幾個(gè)數(shù)量級(jí),即使新生代垃圾回收的頻率更高,執(zhí)行效率也仍然比老年代垃圾回收強(qiáng),這是因?yàn)榇蠖鄶?shù)對(duì)象的生命周期都很短,根本無(wú)需提升到老年代。

golang 中的 gc 通常是如何工作的

golang 中的 gc 基本上是標(biāo)記清除的思路:

在內(nèi)存堆中(由于有的時(shí)候管理內(nèi)存頁(yè)的時(shí)候要用到堆的數(shù)據(jù)結(jié)構(gòu),所以稱為堆內(nèi)存)存儲(chǔ)著有一系列的對(duì)象,這些對(duì)象可能會(huì)與其他對(duì)象有關(guān)聯(lián)(references between these objects) a tracing garbage collector 會(huì)在某一個(gè)時(shí)間點(diǎn)上停止原本正在運(yùn)行的程序,之后它會(huì)掃描 runtim e已經(jīng)知道的的 object 集合(already known set of objects),通常它們是存在于 stack 中的全局變量以及各種對(duì)象。gc 會(huì)對(duì)這些對(duì)象進(jìn)行標(biāo)記,將這些對(duì)象的狀態(tài)標(biāo)記為可達(dá),從中找出所有的,從當(dāng)前的這些對(duì)象可以達(dá)到其他地方的對(duì)象的 reference,并且將這些對(duì)象也標(biāo)記為可達(dá)的對(duì)象,這個(gè)步驟被稱為 mark phase,即標(biāo)記階段,這一步的主要目的是用于獲取這些對(duì)象的狀態(tài)信息。

一旦將所有的這些對(duì)象都掃描完,gc 就會(huì)獲取到所有的無(wú)法 reach 的對(duì)象(狀態(tài)為 unreachable 的對(duì)象),并且將它們回收,這一步稱為 sweep phase,即是清掃階段。

gc 僅僅搜集那些未被標(biāo)記為可達(dá)(reachable)的對(duì)象。如果 gc 沒有識(shí)別出一個(gè) reference,最后有可能會(huì)將一個(gè)仍然在使用的對(duì)象給回收掉,就引起了程序運(yùn)行錯(cuò)誤。

可以看到主要的三個(gè)步驟:掃描,回收,清掃。

感覺比起其他的語(yǔ)言,golang 中的垃圾回收模型還是相對(duì)簡(jiǎn)單的。

gc中的問題

gc 的引入可以說就是為了解決內(nèi)存回收的問題。新開發(fā)的語(yǔ)言(java,python,php等等),在使用的時(shí)候,可以使用戶不必關(guān)心內(nèi)存對(duì)象的釋放,只需要關(guān)心對(duì)象的申請(qǐng)即可,通過在 runtime 或者在 vm 中進(jìn)行相關(guān)的操作,達(dá)到自動(dòng)管理內(nèi)存空間的效果,這種對(duì)不再使用的內(nèi)存資源進(jìn)行自動(dòng)回收的行為就被稱為垃圾回收。

根據(jù)前面的表述,能否正常識(shí)別一個(gè) reference 是 gc 能夠正常工作的基礎(chǔ),因此第一個(gè)問題就是 gc 應(yīng)該如何識(shí)別一個(gè) reference?

的問題:對(duì)于 reference 的識(shí)別比較難,machine code 很難知道,怎樣才算是一個(gè)reference。如果錯(cuò)漏掉了一個(gè) reference,就會(huì)使得,原本沒有準(zhǔn)備好要被 free 掉的內(nèi)存現(xiàn)在被錯(cuò)誤地 free 掉,所以策略就是寧多勿少。

一種策略是把所有的 memory 空間都看做是有可能的 references(指針值)。這種被稱為保守型垃圾回收器(conservative garbage collector)。C 中的 Boehm garbage collector 就是這樣工作的。就是說把內(nèi)存中的普通變量也當(dāng)做指針一樣去處理,盡量 cover 到所有的指針的情況,如果碰巧這個(gè)普通的變量值所指向的空間有其他的對(duì)象,那么這個(gè)對(duì)象是不會(huì)被回收的。而 go 語(yǔ)言實(shí)現(xiàn)是完全知道對(duì)象的類型信息,在標(biāo)記時(shí)只會(huì)遍歷指針指向的對(duì)象,這樣就避免了 C 實(shí)現(xiàn)時(shí)的堆內(nèi)存浪費(fèi)(解決約 10-30% )。

三色標(biāo)記

2014/6 1.3 引入并發(fā)清理(垃圾回收和用戶邏輯并發(fā)執(zhí)行?)

2015/8 1.5 引入三色標(biāo)記法

關(guān)于并發(fā)清理的引入,參照的是這里在 1.3 版本中,go runtime 分離了 mark 和 sweep 的操作,和以前一樣,也是先暫停所有任務(wù)執(zhí)行并啟動(dòng) mark( mark 這部分還是要把原程序停下來的),mark 完成后就馬上就重新啟動(dòng)被暫停的任務(wù)了,并且讓 sweep 任務(wù)和普通協(xié)程任務(wù)一樣并行,和其他任務(wù)一起執(zhí)行。如果運(yùn)行在多核處理器上,go 會(huì)試圖將 gc 任務(wù)放到單獨(dú)的核心上運(yùn)行而盡量不影響業(yè)務(wù)代碼的執(zhí)行,go team 自己的說法是減少了 50%-70% 的暫停時(shí)間。

基本算法就是之前提到的清掃+回收,Golang gc 優(yōu)化的核心就是盡量使得 STW(Stop The World) 的時(shí)間越來越短。

如何測(cè)量 GC

之前說了那么多,那如何測(cè)量 gc 的之星效率,判斷它到底是否對(duì)程序的運(yùn)行造成了影響呢? 第一種方式是設(shè)置 godebug 的環(huán)境變量,具體可以參考這一篇,真的是講的很好的文章:鏈接,比如運(yùn)行GODEBUG=gctrace=1 ./myserver,如果要想對(duì)于輸出結(jié)果了解,還需要對(duì)于 gc 的原理進(jìn)行更進(jìn)一步的深入分析,這篇文章的好處在于,清晰的之處了 golang 的 gc 時(shí)間是由哪些因素決定的,因此也可以針對(duì)性的采取不同的方式提升 gc 的時(shí)間:

根據(jù)之前的分析也可以知道,golang 中的 gc 是使用標(biāo)記清楚法,所以 gc 的總時(shí)間為:

Tgc = Tseq + Tmark + Tsweep( T 表示 time)

Tseq 表示是停止用戶的 goroutine 和做一些準(zhǔn)備活動(dòng)(通常很?。┬枰臅r(shí)間Tmark 是堆標(biāo)記時(shí)間,標(biāo)記發(fā)生在所有用戶 goroutine 停止時(shí),因此可以顯著地影響處理的延遲Tsweep 是堆清除時(shí)間,清除通常與正常的程序運(yùn)行同時(shí)發(fā)生,所以對(duì)延遲來說是不太關(guān)鍵的

之后粒度進(jìn)一步細(xì)分,具體的概念還是有些不太懂:

與 Tmark 相關(guān)的:1 垃圾回收過程中,堆中活動(dòng)對(duì)象的數(shù)量,2 帶有指針的活動(dòng)對(duì)象占據(jù)的內(nèi)存總量 3 活動(dòng)對(duì)象中的指針數(shù)量。與 Tsweep 相關(guān)的:1 堆內(nèi)存的總量 2 堆中的垃圾總量如何進(jìn)行 gc 調(diào)優(yōu)( gopher 大會(huì) Danny )硬性參數(shù)

涉及算法的問題,總是會(huì)有些參數(shù)。GOGC 參數(shù)主要控制的是下一次 gc 開始的時(shí)候的內(nèi)存使用量。

比如當(dāng)前的程序使用了 4M 的對(duì)內(nèi)存(這里說的是堆內(nèi)存),即是說程序當(dāng)前 reachable 的內(nèi)存為 4m,當(dāng)程序占用的內(nèi)存達(dá)到 reachable*(1+GOGC/100)=8M 的時(shí)候,gc 就會(huì)被觸發(fā),開始進(jìn)行相關(guān)的 gc 操作。

如何對(duì) GOGC 的參數(shù)進(jìn)行設(shè)置,要根據(jù)生產(chǎn)情況中的實(shí)際場(chǎng)景來定,比如 GOGC 參數(shù)提升,來減少 GC 的頻率。

小tips

想要有深入的 insights,使用 gdb 時(shí)必不可少的了,這篇文章里面整理了一些 gdb 使用的入門技巧。

減少對(duì)象分配 所謂減少對(duì)象的分配,實(shí)際上是盡量做到,對(duì)象的重用。 比如像如下的兩個(gè)函數(shù)定義:

第一個(gè)函數(shù)沒有形參,每次調(diào)用的時(shí)候返回一個(gè) []byte,第二個(gè)函數(shù)在每次調(diào)用的時(shí)候,形參是一個(gè) buf []byte 類型的對(duì)象,之后返回讀入的 byte 的數(shù)目。

第一個(gè)函數(shù)在每次調(diào)用的時(shí)候都會(huì)分配一段空間,這會(huì)給 gc 造成額外的壓力。第二個(gè)函數(shù)在每次迪調(diào)用的時(shí)候,會(huì)重用形參聲明。

老生常談 string 與 []byte 轉(zhuǎn)化 在 stirng 與 []byte 之間進(jìn)行轉(zhuǎn)換,會(huì)給 gc 造成壓力 通過 gdb,可以先對(duì)比下兩者的數(shù)據(jù)結(jié)構(gòu):

兩者發(fā)生轉(zhuǎn)換的時(shí)候,底層數(shù)據(jù)結(jié)結(jié)構(gòu)會(huì)進(jìn)行復(fù)制,因此導(dǎo)致 gc 效率會(huì)變低。解決策略上,一種方式是一直使用 []byte,特別是在數(shù)據(jù)傳輸方面,[]byte 中也包含著許多 string 會(huì)常用到的有效的操作。另一種是使用更為底層的操作直接進(jìn)行轉(zhuǎn)化,避免復(fù)制行為的發(fā)生??梢詤⒖嘉⑿拧坝旰蹖W(xué)堂”中性能優(yōu)化的第一部分,主要是使用 unsafe.Pointer 直接進(jìn)行轉(zhuǎn)化。

對(duì)于 unsafe 的使用,感覺可以單獨(dú)整理一出一篇文章來了,先把相關(guān)資料列在這里 http://studygolang.com/articles/685 直觀上,可以把 unsafe.Pointer 理解成 c++ 中的 void*,在 golang 中,相當(dāng)于是各種類型的指針進(jìn)行轉(zhuǎn)化的橋梁。

關(guān)于 uintptr 的底層類型是 int,它可以裝下指針?biāo)傅牡刂返闹怠K梢院?unsafe.Pointer 進(jìn)行相互轉(zhuǎn)化,主要的區(qū)別是,uintptr 可以參與指針運(yùn)算,而 unsafe.Pointer 只能進(jìn)行指針轉(zhuǎn)化,不能進(jìn)行指針運(yùn)算。想要用 golang 進(jìn)行指針運(yùn)算,可以參考這個(gè)。具體指針運(yùn)算的時(shí)候,要先轉(zhuǎn)成 uintptr 的類型,才能進(jìn)一步計(jì)算,比如偏移多少之類的。

少量使用+連接 string 由于采用 + 來進(jìn)行 string 的連接會(huì)生成新的對(duì)象,降低 gc 的效率,好的方式是通過 append 函數(shù)來進(jìn)行。

但是還有一個(gè)弊端,比如參考如下代碼:

在使用了append操作之后,數(shù)組的空間由1024增長(zhǎng)到了1312,所以如果能提前知道數(shù)組的長(zhǎng)度的話,最好在最初分配空間的時(shí)候就做好空間規(guī)劃操作,會(huì)增加一些代碼管理的成本,同時(shí)也會(huì)降低gc的壓力,提升代碼的效率。


網(wǎng)站欄目:關(guān)于GolangGC垃圾回收機(jī)制的詳解
瀏覽路徑:http://weahome.cn/article/cpeipg.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部