天天看點

平衡GC線程上的工作

在server gc中,每個GC線程都将并行地在其堆上工作(這是一個簡單化的視圖,不一定對所有階段都适用,但在較高的層次上,這正是并行GC的概念)。是以,僅此一項就意味着工作已經在GC線程之間被分割了。但是,由于某些階段的GC工作隻能在所有線程完成其最後一個階段之後才能繼續(例如,在所有GC線程都完成标記階段之前,我們不能讓任何GC線程從計劃階段開始,這樣我們就不會錯過應該标記的對象),是以我們希望每個線程上的GC工作量盡可能平衡總的暫停時間可以更短,否則,如果一個線程花費很長時間來完成這樣的階段,其他線程将等待不做任何事情。為了使工作更加平衡,我們做了很多事情。我們将繼續做這樣的工作來平衡更多。

平衡配置設定

平衡收集工作的一種方法是平衡配置設定。當然,即使每個堆具有完全相同的配置設定量,收集工作的量仍然可能非常不同,這取決于生存期。但這确實有幫助。是以,我們在GC結束時均衡配置設定預算,以便每個堆獲得相同的配置設定預算。這并不意味着每個堆自然會獲得相同的配置設定量,但它對每個堆在觸發下一個GC之前可以執行的配置設定量設定了相同的上限。當然,配置設定線程的數量和每個配置設定線程所做的配置設定量取決于使用者代碼。我們試圖在與運作配置設定線程的核心相關聯的堆上進行配置設定,但是由于我們沒有控制權,我們需要檢查是否應該平衡到其他最不滿的堆,并在适當的時候平衡它們。“在适當的時候”需要一些仔細的調優啟發。目前,我們要考慮線程運作的核心,它運作的NUMA節點,它與其他堆相比還有多少配置設定預算,以及有多少配置設定線程在同一個核心上運作。我确實認為這有點不必要的複雜,是以我們正在做更多的工作,看看我們是否可以簡單地做到這一點。

如果我們使用GCHeapCount配置來指定比核心更少的堆,這意味着隻有那麼多GC線程,并且預設情況下它們隻能在那麼多的核心上運作。當然,使用者線程可以在其餘核心上自由運作,它們所做的配置設定将平衡到GC堆上。

平衡GC工作

在GC中進行的大多數目前平衡都集中在标記上,因為标記通常是花費最長時間的階段。如果你要選擇要平衡的任務,那麼平衡最容易失衡的最長部分是更有意義的——平衡工作不是沒有代價的。

标記使用一個标記堆棧,這使得它成為工作偷竊的自然目标。當一個GC線程完成了自己的标記後,它會四處檢視其他線程的标記堆棧是否仍在忙,如果仍然忙,則竊取一個對象進行标記。由于我們實作了“部分标記”,這意味着如果一個對象包含許多引用,我們一次隻将其中的一塊推送到标記堆棧上,而不會溢出堆棧。這意味着堆棧上的條目可能不是直接的對象位址。竊取需要識别特定的序列,以确定是否應該搜尋其他條目,或者讀取該序列中正确的條目進行竊取。請注意,這隻在完全封鎖地面軍事系統期間開啟,因為在某些情況下偷竊确實會造成明顯的成本。

性能工作主要由使用者場景驅動。随着我們的架構越來越多地被高性能場景使用,我們一直在努力縮短暫停時間。有人問過并行壓縮GCs,是的,我們的路線圖上确實有。但這并不意味着我們将停止改進我們目前的GC。我們在檢視客戶資料時注意到的一點是,當我們進行短暫的GC時,标記老一代對象所指向的年輕一代對象通常需要花費最長的時間。最近,我們在5.0中實作了工作竊取,通過讓每個GC線程每次都要處理老一代的一大塊。它以原子方式增加塊索引,是以如果另一個線程也在檢視同一代,那麼它将擷取下一個尚未擷取的塊。複雜的是我們可能有多個片段,是以我們需要跟蹤目前正在處理的片段(及其起始索引)。當一個線程剛剛到達一個已經被其他線程處理過的段時,它知道要前進超過這個段。每個塊保證隻由一個線程處理。由于這一保證,而且将指針重新定位到年輕的gen對象共享相同的代碼路徑,這意味着重新定位工作也以同樣的方式得到了平衡。

我們也在階段結束時做平衡工作,這樣就可以平衡在同一階段發生的早期工作中的不平衡。

還有其他類型的平衡,但這些是最主要的。STW-GCs可以平衡更多的工作。我們選擇更多地關注标記階段,因為這是最需要的。我們沒有平衡并發工作,僅僅是因為當你同時運作時它更容易被原諒。很明顯,平衡這一點也是有好處的,是以這是一個去做的問題。

未來的工作

繼續努力使事情更加平衡。除了更多地平衡目前任務外,我們還需要改變堆的組織方式,使平衡更加自然(是以線程與堆的耦合不是那麼緊密)。