常常聽到 Java 性能不如 C/C++ 的言論,也常常據說 Java 程序須要預熱,那麼其中主要緣由是啥呢?面試
面試的時候談到 JVM,也有不少面試官喜歡問,爲啥 Java 程序越執行越快呢?算法
通常人都能回答上來,類加載,緩存預熱等等,可是深刻下去,最重要的卻沒有答上來,今天本系列文章就來幫助你們理解這個問題的關鍵。本篇文章是 TLAB 預熱。緩存
TLAB(Thread Local Allocation Buffer)線程本地分配緩存區,這是一個線程專用的內存分配區域。安全
既然是一個內存分配區域,咱們就先要搞清楚 Java 內存大概是如何分配的。markdown
咱們這裏不考慮棧上分配,這些會在 JIT 的章節詳細分析,咱們這裏考慮的是沒法棧上分配須要共享的對象。多線程
對於 HotSpot JVM 實現,全部的 GC 算法的實現都是一種對於堆內存的管理,也就是都實現了一種堆的抽象,它們都實現了接口 CollectedHeap。當分配一個對象堆內存空間時,在 CollectedHeap 上首先都會檢查是否啓用了 TLAB,若是啓用了,則會嘗試 TLAB 分配;若是當前線程的 TLAB 大小足夠,那麼從線程當前的 TLAB 中分配;若是不夠,可是當前 TLAB 剩餘空間小於最大浪費空間限制(這是一個動態的值,咱們後面會詳細分析),則從堆上(通常是 Eden 區) 從新申請一個新的 TLAB 進行分配。不然,直接在 TLAB 外進行分配。TLAB 外的分配策略,不一樣的 GC 算法不一樣。例如G1:性能
這裏,咱們先只關心 TLAB 分配。 對於單線程應用,每次分配內存,會記錄上次分配對象內存地址末尾的指針,以後分配對象會從這個指針開始檢索分配。這個機制叫作 bump-the-pointer (撞針)。 對於多線程應用來講,內存分配須要考慮線程安全。最直接的想法就是經過全局鎖,可是這個性能會不好。爲了優化這個性能,咱們考慮能夠每一個線程分配一個線程本地私有的內存池,而後採用 bump-the-pointer 機制進行內存分配。這個線程本地私有的內存池,就是 TLAB。只有 TLAB 滿了,再去申請內存的時候,須要擴充 TLAB 或者使用新的 TLAB,這時候才須要鎖。這樣大大減小了鎖使用。優化
根據以前的分析,每一個線程的 TLAB 的大小,會根據線程分配的特性,不斷變化並趨於穩定,大小主要是由分配比例 EMA 決定,可是這個採集是須要必定運行次數的。而且 EMA 的前 100 次採集默認是不夠穩定的,因此 TLAB 大小也在程序一開始的時候變化頻繁。當程序線程趨於穩定,運行一段時間後, 每一個線程 TLAB 大小也會趨於穩定而且調整到最適合這個線程對象分配特性的大小。這樣,就更接近最理想的只有 Eden 區滿了纔會 GC,全部 Eden 區的對象都是經過 TLAB 分配的高效分配狀況。這就是 Java 代碼越執行越快在 TLAB 方面的緣由。spa