今天,又是乾貨滿滿的一天。這是全網最硬核 JVM 系列的開篇,首先從 TLAB 開始。因爲文章很長,每一個人閱讀習慣不一樣,因此特此拆成單篇版和多篇版算法
- 全網最硬核 JVM TLAB 分析(單篇版不包含額外加菜)
- 全網最硬核 JVM TLAB 分析 1. 內存分配思想引入
- 全網最硬核 JVM TLAB 分析 2. TLAB生命週期與帶來的問題思考
- 全網最硬核 JVM TLAB 分析 3. JVM EMA指望算法與TLAB相關JVM啓動參數
- 全網最硬核 JVM TLAB 分析 4. TLAB 基本流程全分析
- 全網最硬核 JVM TLAB 分析 5. TLAB 源代碼全解析
- 全網最硬核 JVM TLAB 分析 6. TLAB 相關熱門Q&A彙總
- 全網最硬核 JVM TLAB 分析(額外加菜) 7. TLAB 相關 JVM 日誌解析
- 全網最硬核 JVM TLAB 分析(額外加菜) 8. 經過 JFR 監控 TLAB
這裏我會持續更新的,解決你們的各類疑問數組
主要保證 GC 的時候掃描高效。因爲 TLAB 僅線程內知道哪些被分配了,在 GC 掃描發生時返回 Eden 區,若是不填充的話,外部並不知道哪一部分被使用哪一部分沒有,須要作額外的檢查,若是填充已經確認會被回收的對象,也就是 dummy object, GC 會直接標記以後跳過這塊內存,增長掃描效率。反正這塊內存已經屬於 TLAB,其餘線程在下次掃描結束前是沒法使用的。這個 dummy object 就是 int 數組。爲了必定能有填充 dummy object 的空間,通常 TLAB 大小都會預留一個 dummy object 的 header 的空間,也是一個 int[]
的 header,因此 TLAB 的大小不能超過int 數組的最大大小,不然沒法用 dummy object 填滿未使用的空間。緩存
當從新分配一個 TLAB 的時候,原有的 TLAB 可能還有空間剩餘。原有的 TLAB 被退回堆以前,須要填充好 dummy object。這樣致使這塊內存沒法分配對象,所示被稱爲「浪費」。若是不限制,遇到 TLAB 剩餘空間不足的狀況就會從新申請,致使分配效率下降,大部分空間被 dummy object 佔滿了,致使 GC 更加頻繁。markdown
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
當分配出來 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 的機制詳情會在另外一個系列說明)
根據以前的分析,每一個線程的 TLAB 的大小,會根據線程分配的特性,不斷變化並趨於穩定,大小主要是由分配比例 EMA 決定,可是這個採集是須要必定運行次數的。而且 EMA 的前 100 次採集默認是不夠穩定的,因此 TLAB 大小也在程序一開始的時候變化頻繁。當程序線程趨於穩定,運行一段時間後, 每一個線程 TLAB 大小也會趨於穩定而且調整到最適合這個線程對象分配特性的大小。這樣,就更接近最理想的只有 Eden 區滿了纔會 GC,全部 Eden 區的對象都是經過 TLAB 分配的高效分配狀況。這就是 Java 代碼越執行越快在 TLAB 方面的緣由。