關於網上各類GO語言GC文章的一些困惑和我的理解

目前網上有不少不錯的介紹GO語言三色標記GC的文章和源碼分析,這裏推薦一篇我的感受寫的比較不錯的從源碼層面解析GO GC的博客Golang 垃圾回收剖析。看這些文章的過程當中也產生了一些困惑,這裏分享一下我的的思考,若是有不許確的地方歡迎你們批評指正。golang

困惑1:什麼是root對象?

介紹go gc的文章都會提到,在三色標記的過程當中,從root對象開始遍歷找出全部的活躍對象,但我收集到的資料裏沒有提到什麼是root。那什麼是root對象呢?函數

  • 全局變量:可執行文件的.data和.bss域記錄了全局變量的內存地址,被這寫內存地址指向的內存是活躍對象。
  • go routine stack:go routine的的局部變量若是分配在堆上,須要GC來管理內存的回收,go routine的stack有相似於程序棧的棧結構,找到經過go routine stack裏記錄的變量的內存地址,能夠找到活躍的局部變量。

經過廣度優先遍歷,能夠從root開始找出全部的活躍對象,也包括被間接引用的對象(即被root直接引用的對象中包含的指針所引用的對象)。
p.s. 分配在程序棧上的對象,會隨着函數調用結束棧指針變化而被操做系統回收,不須要GC來處理。源碼分析

困惑2:怎麼知道一個對象被引用仍是沒有被引用?

被引用
一個活躍的對象包括他所佔用的內存和指向這塊內存的指針,若是程序中存在一個上述的可被追蹤的內存地址指向這塊內存,則對象是活躍的。聲明變量和給變量賦值的時候會改變引用關係。性能

一個對象怎麼從被引用變成不被引用的spa

  1. 一個go routine執行結束了,這個go routine stack上記錄的局部變量的內存地址所引用的對象就不可達了,不會被GC標記爲活躍對象。
  2. 下面的例子中,給變量s從新賦值以後,s這個變量引用的是"bye",而"hello"這個字符串對象沒有被指針引用,佔用的內存就會被GC回收。而經過對象池,複用結構體,每次使用時重置結構體字段的值(此時結構體不會被回收),就能減小分配和回收內存的次數,提升程序的性能。
s := "hello"
s = "bye"
複製代碼

困惑3:GC線程跟用戶線程並行執行標記的時候,有兩次短暫的STW的緣由

這是因爲在GC進行標記的時候,用戶進程還能夠繼續申請新的對象,若是這個新的對象被一個黑色對象引用,因爲GC不會再去掃描黑色的對象,那麼這個新申請的對象就不會被GC掃描到,而被錯誤的清除。
因此Go語言使用寫屏障的機制(write barrier)來記錄GC標記過程當中對這些新申請對象的引用,並在GC標記的最後階段從新掃描(re-scan)這部分變化的引用關係。
在GC開始的時候有一個短暫的STW來開啓寫屏障,能夠理解成生成一個當前內存狀況的"快照",後面能夠根據這個"快照"判斷哪些變化了的引用關係。在GC標記的最後階段的STW就是來執行這個re-scan去處理write barrier記錄下來的變化,此時若是不STW那麼GC標記就會進入死循環永遠沒法完成。操作系統

困惑4:GC清除對象的時候,沒有STW並且是跟用戶線程並行執行的,如何避免對象引用關係變化而致使有對象被錯誤清除的?

這個困惑是在瞭解了Go的內存管理機制後有了答案的。由於Go語言會給對象記錄一個GC generation。GC的並行標記和寫屏障會保證在GC開始前已經存在的對象和標記過程當中新分配的對象具備相同的gc geneartion。而標記結束之後,執行清除的過程當中新產生的對象的GC generation不一樣,GC只會清除具備上次執行標記時的generation的對象。因此以後新產生的對象,在gc清除的時候不管是否被引用,都要等到下一個GC週期纔有可能被清除。線程

相關文章
相關標籤/搜索