全網最硬核 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 更加頻繁。markdown

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

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

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

1/2 * (每一個 epoch 內每一個線程指望 refill 次數) * 100優化

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

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 方面的緣由。

相關文章
相關標籤/搜索