全網最硬核 JVM TLAB 分析 6. TLAB 相關熱門Q&A彙總

今天,又是乾貨滿滿的一天。這是全網最硬核 JVM 系列的開篇,首先從 TLAB 開始。由於文章很長,每個人閱讀習慣不同,所以特此拆成單篇版和多篇版

10. TLAB 流程常見問題 Q&A

這裏我會持續更新的,解決大家的各種疑問

10.1. 爲何 TLAB 在退還給堆的時候需要填充 dummy object

主要保證 GC 的時候掃描高效。由於 TLAB 僅線程內知道哪些被分配了,在 GC 掃描發生時返回 Eden 區,如果不填充的話,外部並不知道哪一部分被使用哪一部分沒有,需要做額外的檢查,如果填充已經確認會被回收的對象,也就是 dummy object, GC 會直接標記之後跳過這塊內存,增加掃描效率。反正這塊內存已經屬於 TLAB,其他線程在下次掃描結束前是無法使用的。這個 dummy object 就是 int 數組。爲了一定能有填充 dummy object 的空間,一般 TLAB 大小都會預留一個 dummy object 的 header 的空間,也是一個 int[] 的 header,所以 TLAB 的大小不能超過int 數組的最大大小,否則無法用 dummy object 填滿未使用的空間。

10.2. 爲何 TLAB 需要最大浪費空間限制

當重新分配一個 TLAB 的時候,原有的 TLAB 可能還有空間剩餘。原有的 TLAB 被退回堆之前,需要填充好 dummy object。這樣導致這塊內存無法分配對象,所示被稱爲“浪費”。如果不限制,遇到 TLAB 剩餘空間不足的情況就會重新申請,導致分配效率降低,大部分空間被 dummy object 佔滿了,導致 GC 更加頻繁。

10.3. 爲何 TLAB 重填次數配置 等於 100 / (2 * TLABWasteTargetPercent)

TLABWasteTargetPercent 描述了初始最大浪費空間配置佔 TLAB 的比例

首先,最理想的情況就是儘量讓所有對象在 TLAB 內分配,也就是 TLAB 可能要佔滿 Eden。 在下次 GC 掃描前,退回 Eden 的內存別的線程是不能用的,因爲剩餘空間已經填滿了 dummy object。所以所有線程使用內存大小就是 下個 epcoh 內會分配對象期望線程個數 * 每個 epoch 內每個線程 refill 次數配置,對象一般都在 Eden 區由某個線程分配,也就所有線程使用內存大小就最好是整個 Eden。但是這種情況太過於理想,總會有內存被填充了 dummy object而造成了浪費,因爲 GC 掃描隨時可能發生。假設平均下來,GC 掃描的時候,每個線程當前的 TLAB 都有一半的內存被浪費,這個每個線程使用內存的浪費的百分比率(也就是 TLABWasteTargetPercent),也就是等於(注意,僅最新的那個 TLAB 有浪費,之前 refill 退回的假設是沒有浪費的):

1/2 * (每個 epoch 內每個線程期望 refill 次數) * 100

那麼每個 epoch 內每個線程 refill 次數配置就等於 50 / TLABWasteTargetPercent, 默認也就是 50 次。

10.4. 爲何考慮 ZeroTLAB

當分配出來 TLAB 之後,根據 ZeroTLAB 配置,決定是否將每個字節賦 0。在 TLAB 申請時,由於申請 TLAB 都發生在對象分配的時候,也就是這塊內存會立刻被使用,並修改賦值。操作內存,涉及到 CPU 緩存行,如果是多核環境,還會涉及到 CPU 緩存行 false sharing,爲了優化,JVM 在這裏做了 Allocation Prefetch,簡單理解就是分配 TLAB 的時候,會盡量加載這塊內存到 CPU 緩存,也就是在分配 TLAB 內存的時候,修改內存是最高效的

在創建對象的時候,本來也要對每個字段賦初始值,大部分字段初始值都是 0,並且,在 TLAB 返還到堆時,剩餘空間填充的也是 int[] 數組,裏面都是 0。

所以,TLAB 剛分配出來的時候,賦 0 避免了後續再賦 0。也能利用好 Allocation prefetch 的機制適應 CPU 緩存行(Allocation prefetch 的機制詳情會在另一個系列說明)

10.5. 爲何 JVM 需要預熱,爲什麼 Java 代碼越執行越快(這裏只提 TLAB 相關的,JIT,MetaSpace,GC等等其他系列會說)

根據之前的分析,每個線程的 TLAB 的大小,會根據線程分配的特性,不斷變化並趨於穩定,大小主要是由分配比例 EMA 決定,但是這個採集是需要一定運行次數的。並且 EMA 的前 100 次採集默認是不夠穩定的,所以 TLAB 大小也在程序一開始的時候變化頻繁。當程序線程趨於穩定,運行一段時間後, 每個線程 TLAB 大小也會趨於穩定並且調整到最適合這個線程對象分配特性的大小。這樣,就更接近最理想的只有 Eden 區滿了纔會 GC,所有 Eden 區的對象都是通過 TLAB 分配的高效分配情況。這就是 Java 代碼越執行越快在 TLAB 方面的原因。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章