90% 的 Java 程序員都說不上來的爲什麼 Java 代碼越執行越快(2)- TLAB預熱

常常聽到 Java 性能不如 C/C++ 的言論,也常常據說 Java 程序須要預熱,那麼其中主要緣由是啥呢面試

面試的時候談到 JVM,也有不少面試官喜歡問,爲啥 Java 程序越執行越快呢算法

通常人都能回答上來,類加載,緩存預熱等等,可是深刻下去,最重要的卻沒有答上來,今天本系列文章就來幫助你們理解這個問題的關鍵。本篇文章是 TLAB 預熱。緩存

TLAB(Thread Local Allocation Buffer)線程本地分配緩存區,這是一個線程專用的內存分配區域。安全

image

既然是一個內存分配區域,咱們就先要搞清楚 Java 內存大概是如何分配的。markdown

image

咱們這裏不考慮棧上分配,這些會在 JIT 的章節詳細分析,咱們這裏考慮的是沒法棧上分配須要共享的對象多線程

對於 HotSpot JVM 實現,全部的 GC 算法的實現都是一種對於堆內存的管理,也就是都實現了一種堆的抽象,它們都實現了接口 CollectedHeap。當分配一個對象堆內存空間時,在 CollectedHeap 上首先都會檢查是否啓用了 TLAB,若是啓用了,則會嘗試 TLAB 分配;若是當前線程的 TLAB 大小足夠,那麼從線程當前的 TLAB 中分配;若是不夠,可是當前 TLAB 剩餘空間小於最大浪費空間限制(這是一個動態的值,咱們後面會詳細分析),則從堆上(通常是 Eden 區) 從新申請一個新的 TLAB 進行分配。不然,直接在 TLAB 外進行分配。TLAB 外的分配策略,不一樣的 GC 算法不一樣。例如G1:性能

  • 若是是 Humongous 對象(對象在超過 Region 一半大小的時候),直接在 Humongous 區域分配(老年代的連續區域)。
  • 根據 Mutator 情況在當前分配下標的 Region 內分配

這裏,咱們先只關心 TLAB 分配。 對於單線程應用,每次分配內存,會記錄上次分配對象內存地址末尾的指針,以後分配對象會從這個指針開始檢索分配。這個機制叫作 bump-the-pointer (撞針)。 對於多線程應用來講,內存分配須要考慮線程安全。最直接的想法就是經過全局鎖,可是這個性能會不好。爲了優化這個性能,咱們考慮能夠每一個線程分配一個線程本地私有的內存池,而後採用 bump-the-pointer 機制進行內存分配。這個線程本地私有的內存池,就是 TLAB。只有 TLAB 滿了,再去申請內存的時候,須要擴充 TLAB 或者使用新的 TLAB,這時候才須要鎖。這樣大大減小了鎖使用。優化

TLAB 初始化

image

TLAB 分配

image

GC 時 TLAB 回收與重計算指望大小

image

爲什麼 Java 代碼越執行越快 - TLAB預熱

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

相關文章
相關標籤/搜索