全網(wǎng)最硬核 JVM 內(nèi)存解析 - 8.元空間的核心概念與設(shè)計(jì)

個人創(chuàng)作公約:本人聲明創(chuàng)作的所有文章皆為自己原創(chuàng),如果有參考任何文章的地方,會標(biāo)注出來,如果有疏漏,歡迎大家批判。如果大家發(fā)現(xiàn)網(wǎng)上有抄襲本文章的,歡迎舉報,并且積極向這個?github 倉庫?提交 issue,謝謝支持~
另外,本文為了避免抄襲,會在不影響閱讀的情況下,在文章的隨機(jī)位置放入對于抄襲和洗稿的人的“親切”的問候。如果是正常讀者看到,筆者在這里說聲對不起,。如果被抄襲狗或者洗稿狗看到了,希望你能夠好好反思,不要再抄襲了,謝謝。
今天又是干貨滿滿的一天,這是全網(wǎng)最硬核 JVM 解析系列第四篇,往期精彩:
全網(wǎng)最硬核 TLAB 解析
全網(wǎng)最硬核 Java 隨機(jī)數(shù)解析
全網(wǎng)最硬核 Java 新內(nèi)存模型解析
本篇是關(guān)于 JVM 內(nèi)存的詳細(xì)分析。網(wǎng)上有很多關(guān)于 JVM 內(nèi)存結(jié)構(gòu)的分析以及圖片,但是由于不是一手的資料亦或是人云亦云導(dǎo)致有很錯誤,造成了很多誤解;并且,這里可能最容易混淆的是一邊是 JVM Specification 的定義,一邊是 Hotspot JVM 的實(shí)際實(shí)現(xiàn),有時候人們一些部分說的是 JVM Specification,一部分說的是 Hotspot 實(shí)現(xiàn),給人一種割裂感與誤解。本篇主要從 Hotspot 實(shí)現(xiàn)出發(fā),以 Linux x86 環(huán)境為主,緊密貼合 JVM 源碼并且輔以各種 JVM 工具驗(yàn)證幫助大家理解 JVM 內(nèi)存的結(jié)構(gòu)。但是,本篇僅限于對于這些內(nèi)存的用途,使用限制,相關(guān)參數(shù)的分析,有些地方可能比較深入,有些地方可能需要結(jié)合本身用這塊內(nèi)存涉及的 JVM 模塊去說,會放在另一系列文章詳細(xì)描述。最后,洗稿抄襲狗不得 house
本篇全篇目錄(以及涉及的 JVM 參數(shù)):
從 Native Memory Tracking 說起(全網(wǎng)最硬核 JVM 內(nèi)存解析 - 1.從 Native Memory Tracking 說起開始)
Native Memory Tracking 的開啟
Native Memory Tracking 的使用(涉及 JVM 參數(shù):
NativeMemoryTracking
)Native Memory Tracking 的 summary 信息每部分含義
Native Memory Tracking 的 summary 信息的持續(xù)監(jiān)控
為何 Native Memory Tracking 中申請的內(nèi)存分為 reserved 和 committed
JVM 內(nèi)存申請與使用流程(全網(wǎng)最硬核 JVM 內(nèi)存解析 - 2.JVM 內(nèi)存申請與使用流程開始)
Linux 大頁分配方式 - Huge Translation Lookaside Buffer Page (hugetlbfs)
Linux 大頁分配方式 - Transparent Huge Pages (THP)
JVM 大頁分配相關(guān)參數(shù)與機(jī)制(涉及 JVM 參數(shù):
UseLargePages
,UseHugeTLBFS
,UseSHM
,UseTransparentHugePages
,LargePageSizeInBytes
)JVM commit 的內(nèi)存與實(shí)際占用內(nèi)存的差異
Linux 下內(nèi)存管理模型簡述
JVM commit 的內(nèi)存與實(shí)際占用內(nèi)存的差異
大頁分配 UseLargePages(全網(wǎng)最硬核 JVM 內(nèi)存解析 - 3.大頁分配 UseLargePages開始)
Java 堆內(nèi)存相關(guān)設(shè)計(jì)(全網(wǎng)最硬核 JVM 內(nèi)存解析 - 4.Java 堆內(nèi)存大小的確認(rèn)開始)
驗(yàn)證?
32-bit
?壓縮指針模式驗(yàn)證?
Zero based
?壓縮指針模式驗(yàn)證?
Non-zero disjoint
?壓縮指針模式驗(yàn)證?
Non-zero based
?壓縮指針模式壓縮對象指針存在的意義(涉及 JVM 參數(shù):
ObjectAlignmentInBytes
)壓縮對象指針與壓縮類指針的關(guān)系演進(jìn)(涉及 JVM 參數(shù):
UseCompressedOops
,UseCompressedClassPointers
)壓縮對象指針的不同模式與尋址優(yōu)化機(jī)制(涉及 JVM 參數(shù):
ObjectAlignmentInBytes
,HeapBaseMinAddress
)通用初始化與擴(kuò)展流程
直接指定三個指標(biāo)的方式(涉及 JVM 參數(shù):
MaxHeapSize
,MinHeapSize
,InitialHeapSize
,Xmx
,Xms
)不手動指定三個指標(biāo)的情況下,這三個指標(biāo)(MinHeapSize,MaxHeapSize,InitialHeapSize)是如何計(jì)算的
壓縮對象指針相關(guān)機(jī)制(涉及 JVM 參數(shù):
UseCompressedOops
)(全網(wǎng)最硬核 JVM 內(nèi)存解析 - 5.壓縮對象指針相關(guān)機(jī)制開始)為何預(yù)留第 0 頁,壓縮對象指針 null 判斷擦除的實(shí)現(xiàn)(涉及 JVM 參數(shù):
HeapBaseMinAddress
)結(jié)合壓縮對象指針與前面提到的堆內(nèi)存限制的初始化的關(guān)系(涉及 JVM 參數(shù):
HeapBaseMinAddress
,ObjectAlignmentInBytes
,MinHeapSize
,MaxHeapSize
,InitialHeapSize
)使用 jol + jhsdb + JVM 日志查看壓縮對象指針與 Java 堆驗(yàn)證我們前面的結(jié)論
堆大小的動態(tài)伸縮(涉及 JVM 參數(shù):
MinHeapFreeRatio
,MaxHeapFreeRatio
,MinHeapDeltaBytes
)(全網(wǎng)最硬核 JVM 內(nèi)存解析 - 6.其他 Java 堆內(nèi)存相關(guān)的特殊機(jī)制開始)適用于長期運(yùn)行并且盡量將所有可用內(nèi)存被堆使用的 JVM 參數(shù) AggressiveHeap
JVM 參數(shù) AlwaysPreTouch 的作用
JVM 參數(shù) UseContainerSupport - JVM 如何感知到容器內(nèi)存限制
JVM 參數(shù) SoftMaxHeapSize - 用于平滑遷移更耗內(nèi)存的 GC 使用
JVM 元空間設(shè)計(jì)(全網(wǎng)最硬核 JVM 內(nèi)存解析 - 7.元空間存儲的元數(shù)據(jù)開始)
jcmd <pid> VM.metaspace
?元空間說明元空間相關(guān) JVM 日志
元空間 JFR 事件詳解
jdk.MetaspaceSummary
?元空間定時統(tǒng)計(jì)事件jdk.MetaspaceAllocationFailure
?元空間分配失敗事件jdk.MetaspaceOOM
?元空間 OOM 事件jdk.MetaspaceGCThreshold
?元空間 GC 閾值變化事件jdk.MetaspaceChunkFreeListSummary
?元空間 Chunk FreeList 統(tǒng)計(jì)事件CommitLimiter
?的限制元空間可以 commit 的內(nèi)存大小以及限制元空間占用達(dá)到多少就開始嘗試 GC每次 GC 之后,也會嘗試重新計(jì)算?
_capacity_until_GC
首先類加載器 1 需要分配 1023 字節(jié)大小的內(nèi)存,屬于類空間
然后類加載器 1 還需要分配 1023 字節(jié)大小的內(nèi)存,屬于類空間
然后類加載器 1 需要分配 264 KB 大小的內(nèi)存,屬于類空間
然后類加載器 1 需要分配 2 MB 大小的內(nèi)存,屬于類空間
然后類加載器 1 需要分配 128KB 大小的內(nèi)存,屬于類空間
新來一個類加載器 2,需要分配 1023 Bytes 大小的內(nèi)存,屬于類空間
然后類加載器 1 被 GC 回收掉
然后類加載器 2 需要分配 1 MB 大小的內(nèi)存,屬于類空間
元空間的整體配置以及相關(guān)參數(shù)(涉及 JVM 參數(shù):
MetaspaceSize
,MaxMetaspaceSize
,MinMetaspaceExpansion
,MaxMetaspaceExpansion
,MaxMetaspaceFreeRatio
,MinMetaspaceFreeRatio
,UseCompressedClassPointers
,CompressedClassSpaceSize
,CompressedClassSpaceBaseAddress
,MetaspaceReclaimPolicy
)元空間上下文?
MetaspaceContext
虛擬內(nèi)存空間節(jié)點(diǎn)列表?
VirtualSpaceList
虛擬內(nèi)存空間節(jié)點(diǎn)?
VirtualSpaceNode
?與?CompressedClassSpaceSize
MetaChunk
類加載的入口?
SystemDictionary
?與保留所有?ClassLoaderData
?的?ClassLoaderDataGraph
每個類加載器私有的?
ClassLoaderData
?以及?ClassLoaderMetaspace
管理正在使用的?
MetaChunk
?的?MetaspaceArena
元空間內(nèi)存分配流程(全網(wǎng)最硬核 JVM 內(nèi)存解析 - 9.元空間內(nèi)存分配流程開始)
ClassLoaderData
?回收ChunkHeaderPool
?池化?MetaChunk
?對象ChunkManager
?管理空閑的?MetaChunk
類加載器到?
MetaSpaceArena
?的流程從?
MetaChunkArena
?普通分配 - 整體流程從?
MetaChunkArena
?普通分配 -?FreeBlocks
?回收老的?current chunk
?與用于后續(xù)分配的流程從?
MetaChunkArena
?普通分配 - 嘗試從?FreeBlocks
?分配從?
MetaChunkArena
?普通分配 - 嘗試擴(kuò)容?current chunk
從?
MetaChunkArena
?普通分配 - 從?ChunkManager
?分配新的?MetaChunk
從?
MetaChunkArena
?普通分配 - 從?ChunkManager
?分配新的?MetaChunk
?- 從?VirtualSpaceList
?申請新的?RootMetaChunk
從?
MetaChunkArena
?普通分配 - 從?ChunkManager
?分配新的?MetaChunk
?- 將?RootMetaChunk
?切割成為需要的?MetaChunk
MetaChunk
?回收 - 不同情況下,?MetaChunk
?如何放入?FreeChunkListVector
什么時候用到元空間,以及釋放時機(jī)
元空間保存什么
什么是元數(shù)據(jù),為什么需要元數(shù)據(jù)
什么時候用到元空間,元空間保存什么
元空間的核心概念與設(shè)計(jì)(全網(wǎng)最硬核 JVM 內(nèi)存解析 - 8.元空間的核心概念與設(shè)計(jì)開始)
元空間分配與回收流程舉例(全網(wǎng)最硬核 JVM 內(nèi)存解析 - 10.元空間分配與回收流程舉例開始)
元空間大小限制與動態(tài)伸縮(全網(wǎng)最硬核 JVM 內(nèi)存解析 - 11.元空間分配與回收流程舉例開始)
jcmd VM.metaspace
?元空間說明、元空間相關(guān) JVM 日志以及元空間 JFR 事件詳解(全網(wǎng)最硬核 JVM 內(nèi)存解析 - 12.元空間各種監(jiān)控手段開始)JVM 線程內(nèi)存設(shè)計(jì)(重點(diǎn)研究 Java 線程)(全網(wǎng)最硬核 JVM 內(nèi)存解析 - 13.JVM 線程內(nèi)存設(shè)計(jì)開始)
解釋執(zhí)行與編譯執(zhí)行時候的判斷(x86為例)
一個 Java 線程 Xss 最小能指定多大
JVM 中有哪幾種線程,對應(yīng)線程棧相關(guān)的參數(shù)是什么(涉及 JVM 參數(shù):
ThreadStackSize
,VMThreadStackSize
,CompilerThreadStackSize
,StackYellowPages
,StackRedPages
,StackShadowPages
,StackReservedPages
,RestrictReservedStack
)Java 線程棧內(nèi)存的結(jié)構(gòu)
Java 線程如何拋出的 StackOverflowError
4. JVM 元空間設(shè)計(jì)
4.3. 元空間的核心概念與設(shè)計(jì)
4.3.1. 元空間的整體配置以及相關(guān)參數(shù)
元空間配置相關(guān)的參數(shù):
MetaspaceSize
:初始元空間大小,也是最小元空間大小。后面元空間大小伸縮的時候,不會小于這個大小。默認(rèn)是 21M。抄襲剽竊侵權(quán)
滾MaxMetaspaceSize
:最大元空間大小,默認(rèn)是無符號 int 最大值。MinMetaspaceExpansion
:每次元空間大小伸縮的時候,至少改變的大小。默認(rèn)是 256K。后文講到元空間內(nèi)存大小限制的時候會詳細(xì)分析。MaxMetaspaceExpansion
:每次元空間大小伸縮的時候,最多改變的大小。默認(rèn)是 4M。后文講到元空間內(nèi)存大小限制的時候會詳細(xì)分析。MaxMetaspaceFreeRatio
:最大元空間空閑比例,默認(rèn)是 70,即 70%。后文講到元空間內(nèi)存大小限制的時候會詳細(xì)分析。MinMetaspaceFreeRatio
:最小元空間空閑比例,默認(rèn)是 40,即 40%。后文講到元空間內(nèi)存大小限制的時候會詳細(xì)分析。UseCompressedClassPointers
:前文提到過,是否開啟壓縮類指針。默認(rèn)是開啟的。老版本中,?UseCompressedClassPointers
?取決于?UseCompressedOops
,即壓縮對象指針如果沒開啟,那么壓縮類指針也無法開啟。但是從 Java 15 Build 23 開始,?UseCompressedClassPointers
?已經(jīng)不再依賴?UseCompressedOops
?了,兩者在大部分情況下已經(jīng)獨(dú)立開來。除非在 x86 的 CPU 上面啟用 JVM Compiler Interface(例如使用 GraalVM)。參考 JDK ISSUE:https://bugs.openjdk.java.net/browse/JDK-8241825 - Make compressed oops and compressed class pointers independent (x86_64, PPC, S390)CompressedClassSpaceSize
:如果啟用了壓縮類指針,則元空間會分為類元空間和數(shù)據(jù)元空間,否則只有數(shù)據(jù)元空間。這個參數(shù)限制類元空間的大小,范圍是 1M ~ 3G。默認(rèn)大小是 1G,如果指定了?MaxMetaspaceSize
,那么為 1G 與?MaxMetaspaceSize * 0.8
?中比較小的那個值,CompressedClassSpaceBaseAddress
:類元空間起始虛擬內(nèi)存地址,這個一般不指定。作用和前文分析堆內(nèi)存的堆起始位置的作用差不多。MetaspaceReclaimPolicy
:可以為:balanced
,?aggressive
, 以及?none
,需要注意一點(diǎn)的是?none
?要被移除了(https://bugs.openjdk.org/browse/JDK-8302385)。默認(rèn)是?balanced
。具體主要是影響元空間底層相關(guān)的配置,下面我們會詳細(xì)分析。
元空間底層相關(guān)的配置包括:
commit 粒度 - commit_granule:通過第二章的分析我們知道,JVM 的空間一般是先 reserve, 之后 commit 之前 reserve 的空間的一部分,然后才能使用的。這個 commit 粒度代表元空間中 commit 內(nèi)存的最小粒度,元空間在擴(kuò)容縮容的時候最小的大小單位是 commit 粒度。
虛擬內(nèi)存空間節(jié)點(diǎn)內(nèi)存大小 - virtual_space_node_default_word_size:這是后文我們會詳細(xì)分析的?
VirtualSpaceNode
?的虛擬內(nèi)存大小。大小在 64 位環(huán)境下是 64 MB。虛擬內(nèi)存空間節(jié)點(diǎn)內(nèi)存對齊 - virtual_space_node_reserve_alignment_words:這是后文我們會詳細(xì)分析的?
VirtualSpaceNode
?的虛擬內(nèi)存大小需要對齊的大小,即整體大小需要大于這個對齊大小并且是這個對齊大小整數(shù)倍。這個大小就是?MetaChunk
?的最大大小,即 4MB。當(dāng)前 MetaChunk 不足以分配的時候,是否嘗試擴(kuò)容當(dāng)前 MetaChunk - enlarge_chunks_in_place:這個參數(shù)在正式 JVM 中是 true,并且不能修改。后文我們會詳細(xì)分析什么是?
MetaChunk
。這里簡單理解就是,元空間整體使用了和 Linux 伙伴分配算法類似的設(shè)計(jì)與抽象,其中內(nèi)存分配的單元就是 Chunk,元空間中對應(yīng)的就是 MetaChunk。分配新的 MetaChunk 的時候,是否一下子 commit MetaChunk 所有的內(nèi)存 - new_chunks_are_fully_committed:后文我們會詳細(xì)分析什么是?
MetaChunk
。在 MetaChunk 整個空間都沒有使用的時候,是否將 MetaChunk 的內(nèi)存全部釋放回操作系統(tǒng) - uncommit_free_chunks:后文我們會詳細(xì)分析什么是?
MetaChunk
。
從 Java 16 開始,引入了彈性元空間。老的元空間由于設(shè)計(jì)上分配粒度比較大,并且沒有很好地釋放空間的策略設(shè)計(jì),所以占用可能比較大。Java 16 開始,JEP 387: Elastic Metaspace 引入了彈性元空間的設(shè)計(jì),也是我們這里要討論的設(shè)計(jì)。這個彈性元空間也引入了一個重要的參數(shù)?-XX:MetaspaceReclaimPolicy
。
MetaspaceReclaimPolicy
:可以為:balanced
,?aggressive
, 以及?none
,需要注意一點(diǎn)的是?none
?要被移除了(https://bugs.openjdk.org/browse/JDK-8302385),這三個配置具體影響是:

4.3.2. 元空間上下文?MetaspaceContext
MetaspaceContext
?本身直接原生堆上面分配,Native Memory Tracking 中屬于?Metaspace
?那一類別,即元空間的抽象類占用的空間。
https://github.com/openjdk/jdk/blob/jdk-21%2B11/src/hotspot/share/memory/metaspace/metaspaceContext.hpp
class MetaspaceContext : public CHeapObj<mtMetaspace>
JVM 元空間,會在全局建立兩個元空間上下文(MetaspaceContext
),一個用于類元空間(我們后面稱為類元空間?MetaspaceContext
),一個用于數(shù)據(jù)元空間(我們后面稱為數(shù)據(jù)元空間?MetaspaceContext
)。當(dāng)然,在沒有啟用壓縮類指針的時候,只會初始化一個數(shù)據(jù)元空間?MetaspaceContext
,不會初始化類元空間?MetaspaceContext
,之后使用分配的時候,也只會用數(shù)據(jù)元空間?MetaspaceContext
?進(jìn)行分配。但是我們在后面討論的時候,只會討論開啟壓縮類指針的情況,因?yàn)檫@是默認(rèn)并且常用的情況。

每個?MetaspaceContext
?都會對應(yīng)一個獨(dú)立的?VirtualSpaceList
,以及一個獨(dú)立的?ChunkManager
。

這個?VirtualSpaceList
?中的每一個元素都是一個?VirtualSpaceNode
。顧名思義,VirtualSpaceNode
?是從操作系統(tǒng)申請內(nèi)存,與元空間內(nèi)存劃分的抽象隔離的中間層抽象。VirtualSpaceList
?負(fù)責(zé)與操作系統(tǒng)交互,申請或者釋放內(nèi)存。元空間與?VirtualSpaceList
?交互,使用內(nèi)存。
ChunkManager
?顧名思義,是管理所有 Chunk 的內(nèi)存管理器。Chunk 這個概念經(jīng)常出現(xiàn)在各種伙伴內(nèi)存管理算法框架(Buddy Allocator)中,一般指內(nèi)存管理分配的最小單元,這里的 Chunk 抽象對應(yīng)的就是?MetaChunk
。ChunkManager
?從?VirtualSpaceList
?上面獲取一塊連續(xù)比較大的內(nèi)存的?MetaChunk
(其實(shí)是?RootMetaChunk
),然后將這個?RootMetaChunk
?按照分配需求,連續(xù)對半分割成需要的大小,返回這個合適大小的?MetaChunk
,剩下的分割出來的?MetaChunk
?進(jìn)入?FreeChunkListVector
?用于下次分配?MetaChunk
?的時候,直接返回合適的,就不再從?VirtualSpaceList
?獲取了。
我們接下來仔細(xì)分析?VirtualSpaceList
?與?ChunkManager
4.3.3. 虛擬內(nèi)存空間節(jié)點(diǎn)列表?VirtualSpaceList
VirtualSpaceList
?本身直接原生堆上面分配,Native Memory Tracking 中屬于?Class
?那一類別,即元空間的加載類占用的空間。其實(shí)本人感覺這么設(shè)計(jì)不太合理,應(yīng)該和?MetaspaceContext
?屬于同一個類別才比較合理。真正分配加載的類的占用空間的是從?VirtualSpaceNode
?上面標(biāo)記的內(nèi)存分配的,這是下一小節(jié)要分析的內(nèi)容。
https://github.com/openjdk/jdk/blob/jdk-21%2B11/src/hotspot/share/memory/metaspace/virtualSpaceList.hpp
class VirtualSpaceList : public CHeapObj<mtClass>
首先提一點(diǎn),類元空間?MetaspaceContext
?與數(shù)據(jù)元空間?MetaspaceContext
?略有不同:類元空間?MetaspaceContext
?的?VirtualSpaceList
?是不可以擴(kuò)展申請新的內(nèi)存的,但是數(shù)據(jù)元空間?MetaspaceContext
?的?VirtualSpaceList
?是可以的。也就是說:類元空間?MetaspaceContext
?的?VirtualSpaceList
?其實(shí)只有一個?VirtualSpaceNode
,但是數(shù)據(jù)元空間?MetaspaceContext
?的?VirtualSpaceList
?是一個包含多個?VirtualSpaceNode
?的列表。

4.3.4. 虛擬內(nèi)存空間節(jié)點(diǎn)?VirtualSpaceNode
?與?CompressedClassSpaceSize
VirtualSpaceNode
?本身直接原生堆上面分配,Native Memory Tracking 中屬于?Class
?那一類別,即元空間的加載類占用的空間。其實(shí)本人感覺這么設(shè)計(jì)不太合理,應(yīng)該和?MetaspaceContext
?屬于同一個類別才比較合理。真正分配加載的類的占用空間的是從?VirtualSpaceNode
?上面標(biāo)記的內(nèi)存地址分配的,VirtualSpaceNode
?本身的空間占用只是起到描述記錄作用,應(yīng)該也屬于元空間描述的那一類。
https://github.com/openjdk/jdk/blob/jdk-21%2B11/src/hotspot/share/memory/metaspace/virtualSpaceNode.hpp
class VirtualSpaceNode : public CHeapObj<mtClass>
VirtualSpaceNode
?是一塊連續(xù)的虛擬內(nèi)存空間內(nèi)存的抽象。類元空間的?VirtualSpaceList
?只包含一個?VirtualSpaceNode
,大小是前文提到的?CompressedClassSpaceSize
。
數(shù)據(jù)元空間并不像類元空間或者堆內(nèi)存那樣,一下子 reserve 最大堆內(nèi)存限制的內(nèi)存,而是每次 reserve?VirtualSpaceNode
?大小。VirtualSpaceNode
?大小在 64 位環(huán)境下是 64 MB:
https://github.com/openjdk/jdk/blob/jdk-21%2B11/src/hotspot/share/memory/metaspace/metaspaceSettings.hpp
static const size_t _virtual_space_node_default_word_size =
? ? ?chunklevel::MAX_CHUNK_WORD_SIZE * NOT_LP64(2) LP64_ONLY(16); // 8MB (32-bit) / 64MB (64-bit)
VirtualSpaceNode
?通過兩個數(shù)據(jù)結(jié)構(gòu)來管理它維護(hù)的虛擬內(nèi)存空間:
CommitMask
:實(shí)際是一個位圖,用于維護(hù)哪些內(nèi)存被 commit 了,哪些沒有,位圖的標(biāo)記的單位就是前文提到的 commit_granule(commit 粒度)。RootChunkAreaLUT
:用于維護(hù)每個?RootMetaChunk
?的內(nèi)存分布。至于什么是?RootMetaChunk
?在后續(xù)我們講?MetaChunk
?的時候會詳細(xì)講解。
一個?VirtualSpaceNode
?的主要結(jié)構(gòu)如下圖所示:

4.3.5.?MetaChunk
MetaChunk
?是元空間內(nèi)存分配的核心抽象,其本質(zhì)就是描述一塊連續(xù)的虛擬內(nèi)存空間。MetaChunk
?本身只是一個描述對象,它也是直接原生堆上面分配,Native Memory Tracking 中屬于?Metaspace
?那一類別,即元空間的抽象類占用的空間。這個描述對象是池化的,參考后面會分析的?ChunkHeaderPool
。不要偷取他人的勞動成果!
元空間的任意分配,都是在某個?MetaChunk
?上進(jìn)行的(不要偷取他人的勞動成果!)。MetaChunk
?有級別的概念,即?ChunkLevel
,每個?MetaChunk
?都有自己的?ChunkLevel
,這個?ChunkLevel
?主要代表了?MetaChunk
?描述的內(nèi)存空間的大小,每一個 level 都是下一個 level 大小的 2 倍:

從?VirtualSpaceNode
?上直接劃分的?MetaChunk
?是?RootMetaChunk
,它的?ChunkLevel
?為最高級別的 0,大小是 4MB,并且其中的內(nèi)存只是 reserve 還沒有 commit 的。
MetaChunk
有三個狀態(tài):
Dead
:即?MetaChunk
?只是對象被創(chuàng)建出來,但是沒有關(guān)聯(lián)描述實(shí)際的虛擬內(nèi)存。后面我們會知道,MetaChunk
?是池化可回收在利用的,MetaChunk
?的池就是?ChunkHeaderPool
。位于?ChunkHeaderPool
?都還沒有關(guān)聯(lián)描述實(shí)際的虛擬內(nèi)存,狀態(tài)為?Dead
。Free
:即?MetaChunk
?關(guān)聯(lián)描述了實(shí)際的虛擬內(nèi)存,但是沒有被實(shí)際使用。此時,這個?MetaChunk
?位于?ChunkManager
?管理。InUse
:即?MetaChunk
?關(guān)聯(lián)描述了實(shí)際的虛擬內(nèi)存,也被實(shí)際使用了,此時,MetaChunkArena
?管理這個?MetaChunk
?上面的內(nèi)存分配。
4.3.5.1.?ChunkHeaderPool
?池化?MetaChunk
?對象
MetaChunk
?實(shí)際上只是一塊連續(xù)的虛擬內(nèi)存空間的描述類(不要偷取他人的勞動成果!),即元數(shù)據(jù)類。由于類加載需要的大小不一,并且還經(jīng)常會發(fā)生合并,切分等等,MetaChunk
?可能有很多很多,元空間為了節(jié)省這個元數(shù)據(jù)類占用的空間,將其池化,回收再利用。這個池就是?ChunkHeaderPool
。例如,從?VirtualSpaceNode
?上直接劃分?RootMetaChunk
?的內(nèi)存空間,會從?ChunkHeaderPool
?申請一個?MetaChunk
?用于描述。當(dāng)兩個?MetaChunk
?的空間需要合并成一個的時候,其中一個?MetaChunk
?其實(shí)就沒有用了,會放回?ChunkHeaderPool
,而不是直接 free 掉這個對象。
ChunkHeaderPool
?本身直接原生堆上面分配,Native Memory Tracking 中屬于?Metaspace
?那一類別,即元空間的抽象類占用的空間。
https://github.com/openjdk/jdk/blob/jdk-21%2B11/src/hotspot/share/memory/metaspace/chunkHeaderPool.hpp
class ChunkHeaderPool : public CHeapObj<mtMetaspace>
其實(shí)從這里我們可以推測出,MetaChunk
?本身也是直接原生堆上面分配,Native Memory Tracking 中也是屬于?Metaspace
?那一類別。
ChunkHeaderPool
?的結(jié)構(gòu)是:

其實(shí)?ChunkHeaderPool
?的機(jī)制很簡單:
申請?
MetaChunk
?用于描述內(nèi)存:首先查看?
_freelist
,是否有之前放回的?MetaChunk
?可以使用,如果有,就返回那個?MetaChunk
,并從?_freelist
?移除這個?MetaChunk
如果沒有,讀取?
_current_slab
?指向的?Slab
,Slab
?核心就是一個預(yù)分配好的?MetaChunk
?數(shù)組(大小是 128),_top
?指的是當(dāng)前使用到數(shù)組的哪一個。如果?
_top
?沒有到 128,返回?_top
?代表的?MetaChunk
,并將?_top
?加 1。如果?
_top
?到 128,創(chuàng)建新的?Slab
,_current_slab
?指向這個新的?Slab
回收?
MetaChunk
:放入?_freelist
4.3.5.2.?ChunkManager
?管理空閑的?MetaChunk
ChunkManager
?本身直接原生堆上面分配,Native Memory Tracking 中屬于?Metaspace
?那一類別,即元空間的抽象類占用的空間。不要偷取他人的勞動成果!
class ChunkManager : public CHeapObj<mtMetaspace>
https://github.com/openjdk/jdk/blob/jdk-21%2B11/src/hotspot/share/memory/metaspace/chunkManager.hpp
ChunkManager
?管理已經(jīng)關(guān)聯(lián)內(nèi)存但是還沒使用(狀態(tài)是?Free
)的?MetaChunk
。在第一次從?VirtualSpaceNode
?上面分配?RootMetaChunk
?的內(nèi)存的時候,根據(jù)申請的內(nèi)存大小,決定要將?RootMetaChunk
?拆分到某個?ChunkLevel
?大小之后用于當(dāng)前分配,拆分出來的其他的?MetaChunk
?還沒有使用,先放入一個類似于之前?ChunkHeaderPool
?里面的?_free_list
?的結(jié)構(gòu),用于下次申請?MetaChunk
?用于分配的時候,先從這個里面找,找不到之后再從?VirtualSpaceNode
?上面嘗試分配新的?RootMetaChunk
。不要慣著cao襲的人!
ChunkManager
?的整體結(jié)構(gòu)是:

ChunkManager
?主要維護(hù)一個?FreeChunkListVector
,FreeChunkListVector
?里面是一個?FreeChunkList
?數(shù)組(還有xigao dog 的碼)。FreeChunkList
?是一個?MetaChunk
?鏈表,鏈表中都是?Free
?的?MetaChunk
,同樣?ChunkLevel
?的?MetaChunk
?位于同一個?FreeChunkList
?中。FreeChunkList
?數(shù)組以?ChunkLevel
?為下標(biāo),這樣的數(shù)據(jù)結(jié)構(gòu)可以快速找到一個所需?ChunkLevel
?的?MetaChunk
。FreeChunkList
這個鏈表其實(shí)是一個雙向鏈表,包含頭尾兩個指針,如果一個?MetaChunk
?管理的內(nèi)存被 commit 了,就會放在鏈表頭部,沒有 commit 的放在鏈表尾部。
MetaChunk
?具體的分配,切分,合并流程,我們會在介紹完?MetaspaceArena
?之后詳細(xì)分析。但是,MetaspaceArena
?和?ChunkManager
?不一樣,ChunkManager
?是全局兩個,一個屬于類元空間,一個屬于數(shù)據(jù)元空間,倘若沒有開啟壓縮類指針,那么就只有一個數(shù)據(jù)元空間?ChunkManager
,而?MetaspaceArena
?我們后面會看到是每個?ClassLoader
?獨(dú)立私有的。所以,在講?MetaspaceArena
?之前,我們先要從另一個角度即?ClassLoader
?加載類的角度出發(fā),向下一層一層剖析到?MetaspaceArena
。
4.3.6. 類加載的入口?SystemDictionary
?與保留所有?ClassLoaderData
?的?ClassLoaderDataGraph
類加載的入口在全局唯一的?SystemDictionary
?中,這里我們只是為了看一下類加載需要哪些參數(shù),來搞清楚對應(yīng)關(guān)系,不用關(guān)心細(xì)節(jié),入口代碼是:
https://github.com/openjdk/jdk/blob/jdk-21%2B11/src/hotspot/share/classfile/systemDictionary.cpp
InstanceKlass* SystemDictionary::resolve_from_stream(ClassFileStream* st,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? Symbol* class_name,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? Handle class_loader,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? const ClassLoadInfo& cl_info,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? TRAPS) {
?//隱藏類與普通類的加載方式不同,隱藏類是 JEP 371: Hidden Classes 引入的,Java 15 中發(fā)布的新特性
?if (cl_info.is_hidden()) {
? ?return resolve_hidden_class_from_stream(st, class_name, class_loader, cl_info, CHECK_NULL);
?} else {
? ?return resolve_class_from_stream(st, class_name, class_loader, cl_info, CHECK_NULL);
?}
}
可以看到,加載類需要以下參數(shù):
ClassFileStream* st
:類文件流Symbol* class_name
:加載的類的名稱Handle class_loader
:是哪個類加載器const ClassLoadInfo& cl_info
:類加載器信息
在加載類的時候,SystemDictionary
?會獲取類加載器的?ClassLoaderData
,ClassLoaderData
?是每個類加載器私有的。
https://github.com/openjdk/jdk/blob/jdk-21%2B11/src/hotspot/share/classfile/systemDictionary.cpp
//通過類加載器獲取對應(yīng)的 `ClassLoaderData`
ClassLoaderData* SystemDictionary::register_loader(Handle class_loader, bool create_mirror_cld) {
?if (create_mirror_cld) {
? ?return ClassLoaderDataGraph::add(class_loader, true);
?} else {
? ?// 如果是 null,代表是 BootstrapClassLoader,使用全局的 BootstrapClassLoader 對應(yīng)的 ClassLoaderData
? ?return (class_loader() == NULL) ? ClassLoaderData::the_null_class_loader_data() :
? ?//否則,從 ClassLoaderDataGraph 尋找或者創(chuàng)建 class_loader 對應(yīng)的 ClassLoaderData
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?ClassLoaderDataGraph::find_or_create(class_loader);
?}
}
ClassLoaderDataGraph
?保存著所有的?ClassLoaderData
,這個主要用來遍歷每個類加載器,以及獲取每個類加載器加載的類的信息,還有遍歷類加載器加載的類,例如?jcmd
?命令中的?VM.classloaders
?以及?VM.classloader_stats
?就是這么實(shí)現(xiàn)的。但是,我們就不糾結(jié)于?ClassLoaderDataGraph
?的細(xì)節(jié)了,這不是咱們的重點(diǎn)。
4.3.7. 每個類加載器私有的?ClassLoaderData
?以及?ClassLoaderMetaspace
ClassLoaderData
?本身直接原生堆上面分配,Native Memory Tracking 中屬于?Class
?那一類別,即元空間的加載類占用的空間。這就很合理了,不加載類就不會有?ClassLoaderData
。
https://github.com/openjdk/jdk/blob/jdk-21%2B11/src/hotspot/share/classfile/classLoaderData.hpp
? ?class ClassLoaderData : public CHeapObj<mtClass>
如前所述,ClassLoaderData
?是每個類加載器私有的。ClassLoaderData
?包含的元素眾多,我們這里只關(guān)心它其中與元空間內(nèi)存分配相關(guān)的,即?ClassLoaderMetaspace
:
https://github.com/openjdk/jdk/blob/jdk-21%2B11/src/hotspot/share/classfile/classLoaderData.hpp
? ClassLoaderMetaspace * volatile _metaspace;
ClassLoaderMetaspace
?本身直接原生堆上面分配,Native Memory Tracking 中屬于?Class
?那一類別,即元空間的加載類占用的空間。
https://github.com/openjdk/jdk/blob/jdk-21%2B11/src/hotspot/share/memory/classLoaderMetaspace.hpp
? ?class ClassLoaderMetaspace : public CHeapObj<mtClass>
ClassLoaderMetaspace
?有不同的類型(MetaspaceType
):
MetaspaceType::StandardMetaspaceType
:平臺類加載器(Platform ClassLoader,Java 9 之前叫做 ext ClassLoader)以及應(yīng)用類加載器(Application ClassLoader)的?ClassLoaderMetaspace
MetaspaceType::BootMetaspaceType
:即根類加載器(Boostrap ClassLoader)的?ClassLoaderMetaspace
MetaspaceType::ClassMirrorHolderMetaspaceType
:加載匿名類的類加載器的?ClassLoaderMetaspace
MetaspaceType::ReflectionMetaspaceType
:反射調(diào)用的前幾次通過 jni native 調(diào)用,超過一定次數(shù)會優(yōu)化成生成字節(jié)碼類調(diào)用。加載這些字節(jié)碼類的類加載器是?jdk.internal.reflect.DelegatingClassLoader
,這個類加載器的?ClassLoaderMetaspace
?類型就是?ReflectionMetaspaceType
。
ClassLoaderMetaspace
?和?MetaspaceContext
?類似,如果壓縮類指針開啟,那么?ClassLoaderMetaspace
?包含一個類元空間的?MetaspaceArena
?和一個數(shù)據(jù)元空間的?MetaspaceArena
,否則只有一個數(shù)據(jù)元空間的?MetaspaceArena
。

4.3.8. 管理正在使用的?MetaChunk
?的?MetaspaceArena
MetaspaceArena
?本身直接原生堆上面分配,Native Memory Tracking 中屬于?Class
?那一類別,即元空間的加載類占用的空間。這也是肯定的,因?yàn)楦惣虞d器存在
class MetaspaceArena : public CHeapObj<mtClass>
MetaspaceArena
?結(jié)構(gòu)如下所示:

MetaspaceArena
?包含:
一個?
MetachunkList
:管理在該?MetaspaceArena
?分配的?MetaChunk
?的列表,列表的第一個是當(dāng)前分配內(nèi)存的?MetaChunk
。當(dāng)前?
MetaspaceArena
?的?ArenaGrowthPolicy
:在當(dāng)前分配內(nèi)存的?MetaChunk
?不夠分配的時候,申請新的?MetaChunk
?的大小。Freeblocks
: 在當(dāng)前分配內(nèi)存的?MetaChunk
?不夠分配的時候,需要分配新的?MetaChunk
。當(dāng)前的?MetaChunk
?剩余空間放入?Freeblocks
。
Freeblocks
?包含一個?BinList32
?和一個?BlockTree
。大小大于 33 字節(jié)的進(jìn)入?BlockTree
,否則進(jìn)入?BinList32
。
BinList32
?類似于?FreeChunkListVector
,是一個鏈表的數(shù)組,同樣大小的內(nèi)存在同一數(shù)組下標(biāo)的鏈表。

BlockTree
?是一個在?Binary Search Tree(BST)的基礎(chǔ)上,同樣內(nèi)存的節(jié)點(diǎn)在二叉樹節(jié)點(diǎn)的后面形成鏈表的數(shù)據(jù)結(jié)構(gòu)。

不同的類加載器類型的類元空間的?MetaspaceArena
?與數(shù)據(jù)元空間的?MetaspaceArena
?的?ArenaGrowthPolicy
?不同:
1.根類加載器(Boostrap ClassLoader)的?ClassLoaderMetaspace
?類元空間的?MetaspaceArena
?的?ArenaGrowthPolicy
:MetachunkList
每次增長都是申請大小為?256K
?的?MetaChunk
https://github.com/openjdk/jdk/blob/jdk-21%2B12/src/hotspot/share/memory/metaspace/metaspaceArenaGrowthPolicy.cpp
? ?static const chunklevel_t g_sequ_boot_class[] = {
? ? ? ?chunklevel::CHUNK_LEVEL_256K
? ? ? ?// .. repeat last
? ?};
2.根類加載器(Boostrap ClassLoader)的?ClassLoaderMetaspace
?數(shù)據(jù)元空間的?MetaspaceArena
?的?ArenaGrowthPolicy
:MetachunkList
?的第一個?MetaChunk
?大小為?4M
,之后每個新?MetaChunk
?都是?1M
:
https://github.com/openjdk/jdk/blob/jdk-21%2B12/src/hotspot/share/memory/metaspace/metaspaceArenaGrowthPolicy.cpp
? ?static const chunklevel_t g_sequ_boot_non_class[] = {
? ? ? ?chunklevel::CHUNK_LEVEL_4M,
? ? ? ?chunklevel::CHUNK_LEVEL_1M
? ? ? ?// .. repeat last
? ?};
3.平臺類加載器(Platform ClassLoader,Java 9 之前叫做 ext ClassLoader)以及應(yīng)用類加載器(Application ClassLoader)的?ClassLoaderMetaspace
?類元空間的?MetaspaceArena
?的?ArenaGrowthPolicy
:MetachunkList
?的第一個?MetaChunk
?大小為?2K
,第二個也是?2K
,第三個?4K
,第四個為?8K
,之后每個新?MetaChunk
?都是?16K
(不要慣著cao襲的人?。?/p>
https://github.com/openjdk/jdk/blob/jdk-21%2B12/src/hotspot/share/memory/metaspace/metaspaceArenaGrowthPolicy.cpp
? ?static const chunklevel_t g_sequ_standard_class[] = {
? ? ? ?chunklevel::CHUNK_LEVEL_2K,
? ? ? ?chunklevel::CHUNK_LEVEL_2K,
? ? ? ?chunklevel::CHUNK_LEVEL_4K,
? ? ? ?chunklevel::CHUNK_LEVEL_8K,
? ? ? ?chunklevel::CHUNK_LEVEL_16K
? ? ? ?// .. repeat last
? ?};
4.平臺類加載器(Platform ClassLoader,Java 9 之前叫做 ext ClassLoader)以及應(yīng)用類加載器(Application ClassLoader)的?ClassLoaderMetaspace
?數(shù)據(jù)元空間的?MetaspaceArena
?的?ArenaGrowthPolicy
:MetachunkList
?的第一個?MetaChunk
?大小為?4K
,第二個也是?4K
,第三個?4K
,第四個為?8K
,之后每個新?MetaChunk
?都是?16K
:
https://github.com/openjdk/jdk/blob/jdk-21%2B12/src/hotspot/share/memory/metaspace/metaspaceArenaGrowthPolicy.cpp
? ?static const chunklevel_t g_sequ_standard_non_class[] = {
? ? ? ?chunklevel::CHUNK_LEVEL_4K,
? ? ? ?chunklevel::CHUNK_LEVEL_4K,
? ? ? ?chunklevel::CHUNK_LEVEL_4K,
? ? ? ?chunklevel::CHUNK_LEVEL_8K,
? ? ? ?chunklevel::CHUNK_LEVEL_16K
? ? ? ?// .. repeat last
? ?};
5.加載匿名類的類加載器的?ClassLoaderMetaspace
?類元空間的?MetaspaceArena
?的?ArenaGrowthPolicy
:MetachunkList
?每次增長都是申請大小為?1K
?的?MetaChunk
:
https://github.com/openjdk/jdk/blob/jdk-21%2B12/src/hotspot/share/memory/metaspace/metaspaceArenaGrowthPolicy.cpp
? ?static const chunklevel_t g_sequ_anon_class[] = {
? ? ? ?chunklevel::CHUNK_LEVEL_1K,
? ? ? ?// .. repeat last
? ?};
6.加載匿名類的類加載器的?ClassLoaderMetaspace
?數(shù)據(jù)元空間的?MetaspaceArena
?的?ArenaGrowthPolicy
:MetachunkList
?每次增長都是申請大小為?1K
?的?MetaChunk
:
https://github.com/openjdk/jdk/blob/jdk-21%2B12/src/hotspot/share/memory/metaspace/metaspaceArenaGrowthPolicy.cpp
? ?static const chunklevel_t g_sequ_anon_non_class[] = {
? ? ? chunklevel::CHUNK_LEVEL_1K,
? ? ? // .. repeat last
? ?};
7.DelegatingClassLoader
?的?ClassLoaderMetaspace
?類元空間的?MetaspaceArena
?的?ArenaGrowthPolicy
:MetachunkList
?每次增長都是申請大小為?1K
?的?MetaChunk
:
https://github.com/openjdk/jdk/blob/jdk-21%2B12/src/hotspot/share/memory/metaspace/metaspaceArenaGrowthPolicy.cpp
? ?static const chunklevel_t g_sequ_refl_class[] = {
? ? ? ?chunklevel::CHUNK_LEVEL_1K,
? ? ? ?// .. repeat last
? ?};
8.DelegatingClassLoader
?的?ClassLoaderMetaspace
?數(shù)據(jù)元空間的?MetaspaceArena
?的?ArenaGrowthPolicy
:MetachunkList
?的第一個?MetaChunk
?大小為?2K
,之后每個新?MetaChunk
?都是?1K
:
https://github.com/openjdk/jdk/blob/jdk-21%2B12/src/hotspot/share/memory/metaspace/metaspaceArenaGrowthPolicy.cpp
? ?static const chunklevel_t g_sequ_refl_non_class[] = {
? ? ? ?chunklevel::CHUNK_LEVEL_2K,
? ? ? ?chunklevel::CHUNK_LEVEL_1K
? ? ? ?// .. repeat last
? ?};
微信搜索“干貨滿滿張哈?!标P(guān)注公眾號,加作者微信,每日一刷,輕松提升技術(shù),斬獲各種offer
我會經(jīng)常發(fā)一些很好的各種框架的官方社區(qū)的新聞視頻資料并加上個人翻譯字幕到如下地址(也包括上面的公眾號),歡迎關(guān)注:
知乎:https://www.zhihu.com/people/zhxhash