全網最硬核 JVM TLAB 分析 1. 內存分配思想引入

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

1. 觀前提醒

本期內容比較硬核,很是全面,涉及到了設計思想到實現原理以及源碼,而且還給出了相應的日誌以及監控方式,若是有不清楚或者有疑問的地方,歡迎留言。markdown

其中涉及到的設計思想主要爲我的理解,實現原理以及源碼解析也是我的整理,若是有不許確的地方,很是歡迎指正!提早感謝~~多線程

2. 分配內存實現思路

咱們常常會 new 一個對象,這個對象是須要佔用空間的,第一次 new 一個對象佔用的空間如 圖00 所示,post

MetaSpace

咱們這裏先只關心堆內部的存儲,元空間中的存儲,咱們會在另外一個系列詳細討論。堆內部的存儲包括對象頭,對象體以及內存對齊填充,那麼這塊空間是如何分配的呢?spa

首先,對象所需的內存,在對象的類被解析加載進入元空間以後,就能夠在分配內存建立前計算出來。假設如今咱們本身來設計堆內存分配,一種最簡單的實現方式就是線性分配,也被稱爲撞針分配(bump-the-pointer)。線程

image

每次須要分配內存時,先計算出須要的內存大小,而後 CAS 更新圖01 中所示的內存分配指針,標記分配的內存。可是內存通常不是這麼整齊的,可能有些內存在分配有些內存就被釋放回收了。因此通常不會只靠撞針分配。一種思路是在撞針分配的基礎上,加上一個 FreeList。設計

image

簡單的實現是將釋放的對象內存加入 FreeList,下次分配對象的時候,優先從 FreeList 中尋找合適的內存大小進行分配,以後再在主內存中撞針分配。3d

這樣雖然必定程度上解決了問題,可是目前大多數應用是多線程的,因此內存分配是多線程的,都從主內存中分配,CAS 更新重試過於頻繁致使效率低下。目前的應用,通常根據不一樣業務區分了不一樣的線程池,在這種狀況下,通常每一個線程分配內存的特性是比較穩定的。這裏的比較穩定指的是,每次分配對象的大小,每輪 GC 分配區間內的分配對象的個數以及總大小。因此,咱們能夠考慮每一個線程分配內存後,就將這塊內存保留起來,用於下次分配,這樣就不用每次從主內存中分配了。若是能估算每輪 GC 內每一個線程使用的內存大小,則能夠提早分配好內存給線程,這樣就更能提升分配效率。這種內存分配的實現方式,在 JVM 中就是 TLAB (Thread Local Allocate Buffer)。指針

3. JVM 對象堆內存分配流程簡述

image

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

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

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