全網最硬核 JVM TLAB 分析 2. TLAB生命週期與帶來的問題思考

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

4. TLAB 的生命週期

image

TLAB 是線程私有的,線程初始化的時候,會建立並初始化 TLAB。同時,在 GC 掃描對象發生以後,線程第一次嘗試分配對象的時候,也會建立並初始化 TLAB。 TLAB 生命週期中止(TLAB 聲明週期中止不表明內存被回收,只是表明這個 TLAB 再也不被這個線程私有管理)在:性能

  • 當前 TLAB 不夠分配,而且剩餘空間小於最大浪費空間限制,那麼這個 TLAB 會被退回 Eden,從新申請一個新的
  • 發生 GC 的時候,TLAB 被回收。

5. TLAB 要解決的問題以及帶來的問題與解決方案的思考

TLAB 要解決的問題很明顯,儘可能避免從堆上直接分配內存從而避免頻繁的鎖爭用。.net

引入 TLAB 以後,TLAB 的設計上,也有不少值得考慮的問題。線程

5.1. 引入 TLAB 後,會有內存孔隙問題,還可能影響 GC 掃描性能

出現孔隙的狀況:設計

  • 當前 TLAB 不夠分配時,若是剩餘空間小於最大浪費空間限制,那麼這個 TLAB 會被退回 Eden,從新申請一個新的。這個剩餘空間就會成爲孔隙。
  • 當發生 GC 的時候,TLAB 沒有用完,沒有分配的內存也會成爲孔隙。

image

若是無論這些孔隙,因爲 TLAB 僅線程內知道哪些被分配了,在 GC 掃描發生時返回 Eden 區,若是不填充的話,外部並不知道哪一部分被使用哪一部分沒有,須要作額外的檢查,那麼會影響 GC 掃描效率。因此 TLAB 迴歸 Eden 的時候,會將剩餘可用的空間用一個 dummy object 填充滿。若是填充已經確認會被回收的對象,也就是 dummy object, GC 會直接標記以後跳過這塊內存,增長掃描效率。可是同時,因爲須要填充這個 dummy object,因此須要預留出這個對象的對象頭的空間日誌

5.2. 某個線程在一輪 GC 內分配的內存並不穩定

若是咱們能提早知道在這一輪內每一個線程會分配多少內存,那麼咱們能夠直接提早分配好。可是,這簡直是癡人說夢。每一個線程在每一輪 GC 的分配狀況可能都是不同的:對象

  • 不一樣的線程業務場景不一樣致使分配對象大小不一樣。咱們通常會按照業務區分不一樣的線程池,作好線程池隔離。對於用戶請求,每次分配的對象可能比較小。對於後臺分析請求,每次分配的對象相對大一些。
  • 不一樣時間段內線程壓力並不均勻。業務是有高峯有低谷的,高峯時間段內確定分配對象更多。
  • 同一時間段同一線程池內的線程的業務壓力也不必定不能作到很均勻。極可能只有幾個線程很忙,其餘線程很閒。

因此,綜合考慮以上狀況,咱們應該這麼實現 TLAB:blog

  • 不能一會兒就給一個線程申請一個比較大的 TLAB,而是考慮這個線程 TLAB 分配滿以後再申請新的,這樣更加靈活。
  • 每次申請 TLAB 的大小是變化的,並非固定的。
  • 每次申請 TLAB 的大小須要考慮當前 GC 輪次內會分配對象的線程的個數指望
  • 每次申請 TLAB 的大小須要考慮全部線程指望 TLAB 分配滿從新申請新的 TLAB 次數
相關文章
相關標籤/搜索