iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 6
0
Modern Web

續說 Ruby on Rails系列 第 6

[Day 6] Ruby 記憶體管理 GC vs. ObjectSpace

GC 介紹

GC 全名是 Garbage Collection(記憶體回收器),是Ruby 用來管理記憶體集區的機制,讓我們不用手動去管理記憶體。
你說為什麼需要GC? 因為每當我們建立一個物件,就需要一塊記憶體,如果都不管他,你的記憶體就會被用完,然後你的電腦就當掉了

為了實現記憶體管理的目的,需要做到兩件事情:

  1. 當需要記憶體時,直接給我們,用完後不用管後續。(申請)
  2. 自動回收沒有被使用的垃圾物件。(回收)

GC 是怎麼運作的?

Ruby GC 使用的過程是「標示並清理」,由兩階段構成。第一階段尋訪物件圖,如果可以在程式碼取得的物件,就是活的物件,並加以「標示」。沒在第一階段被標示的物件就是垃圾物件,也就是不會被使用的物件,之後應該被清理回收,比如在Rails中一次請求中產生的臨時物件,當請求完成後,這些物件就不會被再次使用了,就會被清理掉,並把記憶體還給Ruby及作業系統。它在 ObjectSpace 所佔用的槽就應該讓出來給新創建的物件使用。

Ruby 的記憶體集區就是堆積(Heap),堆積被劃分成分頁(Page),分頁又再劃分成凹槽(Slot),每個凹槽用來儲存單一的Ruby物件。

標記算法,從根物件開始,遍歷所有根物件及其引用的物件,將有用的物件標記一下(修改RVALUE中的一個bit)。這個過程完成之後,就開始清除算法,將所有沒有被標記為有用的物件全部回收。


(根物件遍歷示意圖)

根物件是當前Ruby明顯需要的,包括:

  1. 全域變數(類別,常數等)
  2. 區域變數(Ruby 當前堆棧信息)
  3. C 記憶體資料暫存器及堆棧數據
  4. 其他

在清除時需要停止程式(Stop the world),也就是清除過程中的長暫停。


ObjectSpace

在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為單位來存儲數據,因此在存儲空間方面,可以大大節省。

ObjectSpace 的方法

_id2ref(object_id) → an object

輸入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

count_objects([result_hash]) → hash

計算所有物件數量,再分類

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
 } 
  1. keys 開頭是 :T_ 代表 live objects. 後面是物件的分類
    例如 :T_ARRAY 是還活著的array數目
  2. :FREE 代表還沒被用過的 object slots(位置)
  3. :TOTAL 代表 1.跟2.的總和

Garbage Collection (GC) 記憶體回收機制

指令

  1. GC.stat
  2. GC.start
    運作如下:

GC.stat

查看GC的狀態

GC.start

(等同 ObjectSpace.garbage_collect)
啟動 garbage collection,full垃圾回收(也就是major gc)

經過 GC.start 後,再用GC.stat來看GC目前的狀態:

參數介紹

  1. count (值從94變成95)
    count 是GC運行的次數,它是由minor_gc_count + major_gc_count 來組成的。其中minor_gc_count指的是GC進行的一次小型的回收,而major_gc_count則是GC進行的是一次full垃圾回收。之所以這樣區分開來,是因為每次minor回收所需要使用的時間遠小於major使用的時間。每次進行GC回收的時候,會消耗很大的資源和性能。根據情況進行minor回收能夠較好的實現性能的提升

  2. heap_live_slot
    在GC運行以來,還存活的物件的個數

  3. heap_free_slots
    用來存放新的物件的slot的個數。

  4. mallocmemory allocation
    用來配置指定大小的記憶體空間

  5. eden_page (伊甸頁)
    標記的過程就是尋找那些需要進一步存活下去的物件。所有的物件都分佈在ObjectSpace 池的頁中。一次標記之後,物件就可以被分為兩類,一種是標記存活的,第二種是沒有被標記為存活的。
    ObjectSpace中的頁也分為兩類,一種是至少包含一個存活物件的頁,也記就是eden_page。

  6. tomb pages (墳墓頁)
    第二種頁,物件全部都是未被標記存活的。當伊甸頁中的物件全部都不會繼續存活時,這頁會被移動到墳墓頁中。當伊甸頁中沒有空閒的槽時,這些墳墓頁又會被重新利用。這樣可以降低ObjectSpace中的伊甸頁的記憶體碎片。當消耗了太多記憶體時,墳墓頁所佔的記憶體我也可能會還給操作系統。但大部分情況下都不會還給操作系統。因為申請記憶體是非常昂貴的操作,而且說不定剛歸還了記憶體,又突然需要它。為了性能就暫時保留在堆上了,這也是很多人說 GC 從來不歸還記憶體給操作系統的原因。

  7. **old_objects **
    年老物件,可以看到經過GC.start之後,年老物件從 305149 變成295504,減少不少。

結語

Ruby在短時間內大量物件被創建的時候,是會申請較多的記憶體來處理的。即使在釋放掉相關物件後不將這部分記憶體歸還給操作系統,因為ruby在下次分配物件的時候不需要時刻都向操作系統請求記憶體資源,而可以直接使用原來使用過的free_slot。

  • 盡量避免創建極大的物件; 盡量復用舊物件。
  • 在rails和ruby中,盡量不要把物件賦予給全局的變量或是類或module的屬性,這會導致這些物件一直被保留而無法釋放。
  • 盡量使用最新版本的Ruby,GC的性能會更好

##參考文件

  1. https://my.oschina.net/u/3970558/blog/2196400
  2. http://patshaughnessy.net/2012/3/23/why-you-should-be-excited-about-garbage-collection-in-ruby-2-0
  3. https://www.ruby-china.org/topics/37118
  4. https://ruby-hacking-guide.github.io/gc.html

上一篇
[Day 5] 尋找 bottleneck
下一篇
[Day 7] Ruby destructive vs. non-destructive method
系列文
續說 Ruby on Rails10
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言