GC 全名是 Garbage Collection(記憶體回收器),是Ruby 用來管理記憶體集區的機制,讓我們不用手動去管理記憶體。
你說為什麼需要GC? 因為每當我們建立一個物件,就需要一塊記憶體,如果都不管他,你的記憶體就會被用完,然後你的電腦就當掉了
為了實現記憶體管理的目的,需要做到兩件事情:
Ruby GC 使用的過程是「標示並清理」,由兩階段構成。第一階段尋訪物件圖,如果可以在程式碼取得的物件,就是活的物件,並加以「標示」。沒在第一階段被標示的物件就是垃圾物件,也就是不會被使用的物件,之後應該被清理回收,比如在Rails中一次請求中產生的臨時物件,當請求完成後,這些物件就不會被再次使用了,就會被清理掉,並把記憶體還給Ruby及作業系統。它在 ObjectSpace 所佔用的槽就應該讓出來給新創建的物件使用。
Ruby 的記憶體集區就是堆積(Heap),堆積被劃分成分頁(Page),分頁又再劃分成凹槽(Slot),每個凹槽用來儲存單一的Ruby物件。
標記算法,從根物件開始,遍歷所有根物件及其引用的物件,將有用的物件標記一下(修改RVALUE中的一個bit)。這個過程完成之後,就開始清除算法,將所有沒有被標記為有用的物件全部回收。
(根物件遍歷示意圖)
根物件是當前Ruby明顯需要的,包括:
在清除時需要停止程式(Stop the world),也就是清除過程中的長暫停。
在Ruby堆空間中維護一個物件池,使用的所有Ruby物件都是從這個池子中取出的,Ruby 也會去清理池子中已經沒有被使用的物件,達到循環利用的目的。
ObjectSpace物件池是由很多堆頁(page) 構成的,每一個頁的大小為16Kb。每頁中包含408 個槽(slot)。一個槽對應一個物件,每一個物件都在物件池中佔有一個槽,每一個槽中躺的是一個RVALUE結構體。
當物件比較小時,比如一個小的字串hello,它可以被直接嵌入到結構體中。但是如果字串非常大時,結構體就放不下。這時候會到Ruby堆中額外申請一塊略大的記憶體,將數據放到這記憶體中,然後在RVALUE中只存放這塊記憶體的指針(flag)。
這些數據可以通過下面的方式得到:
在rials console 執行
GC::INTERNAL_CONSTANTS
# => {
:RVALUE_SIZE=>40, # 每個RValue結構體的大小是40bytes
:HEAP_PAGE_OBJ_LIMIT=>408, # 每頁中包含408 個槽
:HEAP_PAGE_BITMAP_SIZE=>56,
:HEAP_PAGE_BITMAP_PLANES=>4,
}
Rstring 結構體,為儲存物件的單位(RArray,RHash,RFile 也是)
Ruby將一些較小的常用的String, Array或Hash等物件存放在此處。
例如:這是一個Ruby堆矩陣的概念圖,包含三個字串以及許多其他RValue:
ps.
所謂的Bit-map就是用一個bit位來標記某個元素對應的Value, 而Key即是該元素。由於採用了Bit為單位來存儲數據,因此在存儲空間方面,可以大大節省。
輸入object_id 可以得到對應的物件
s = "I am a string" #=> "I am a string"
s.object_id #=> 70329011079560
r = ObjectSpace._id2ref(s.object_id) #=> "I am a string"
r == s #=> true
計算所有物件數量,再分類
ObjectSpace.count_objects({})
=>
{
:TOTAL=>413314, :FREE=>104928, :T_OBJECT=>7094,
:T_CLASS=>7052, :T_MODULE=>997, :T_FLOAT=>9,
:T_STRING=>142438, :T_REGEXP=>1551, :T_ARRAY=>52680,
:T_HASH=>5355, :T_STRUCT=>677, :T_BIGNUM=>17,
:T_FILE=>13, :T_DATA=>5060, :T_MATCH=>901,
:T_COMPLEX=>1, :T_RATIONAL=>910, :T_SYMBOL=>574,
:T_IMEMO=>81409, :T_ICLASS=>1648
}
指令
查看GC的狀態
(等同 ObjectSpace.garbage_collect
)
啟動 garbage collection,full垃圾回收(也就是major gc)
經過 GC.start 後,再用GC.stat
來看GC目前的狀態:
count (值從94變成95)
count 是GC運行的次數,它是由minor_gc_count + major_gc_count 來組成的。其中minor_gc_count指的是GC進行的一次小型的回收,而major_gc_count則是GC進行的是一次full垃圾回收。之所以這樣區分開來,是因為每次minor回收所需要使用的時間遠小於major使用的時間。每次進行GC回收的時候,會消耗很大的資源和性能。根據情況進行minor回收能夠較好的實現性能的提升
heap_live_slot
在GC運行以來,還存活的物件的個數
heap_free_slots
用來存放新的物件的slot的個數。
malloc 是 memory allocation
用來配置指定大小的記憶體空間
eden_page (伊甸頁)
標記的過程就是尋找那些需要進一步存活下去的物件。所有的物件都分佈在ObjectSpace 池的頁中。一次標記之後,物件就可以被分為兩類,一種是標記存活的,第二種是沒有被標記為存活的。
ObjectSpace中的頁也分為兩類,一種是至少包含一個存活物件的頁,也記就是eden_page。
tomb pages (墳墓頁)
第二種頁,物件全部都是未被標記存活的。當伊甸頁中的物件全部都不會繼續存活時,這頁會被移動到墳墓頁中。當伊甸頁中沒有空閒的槽時,這些墳墓頁又會被重新利用。這樣可以降低ObjectSpace中的伊甸頁的記憶體碎片。當消耗了太多記憶體時,墳墓頁所佔的記憶體我也可能會還給操作系統。但大部分情況下都不會還給操作系統。因為申請記憶體是非常昂貴的操作,而且說不定剛歸還了記憶體,又突然需要它。為了性能就暫時保留在堆上了,這也是很多人說 GC 從來不歸還記憶體給操作系統的原因。
**old_objects **
年老物件,可以看到經過GC.start
之後,年老物件從 305149 變成295504,減少不少。
Ruby在短時間內大量物件被創建的時候,是會申請較多的記憶體來處理的。即使在釋放掉相關物件後不將這部分記憶體歸還給操作系統,因為ruby在下次分配物件的時候不需要時刻都向操作系統請求記憶體資源,而可以直接使用原來使用過的free_slot。
##參考文件