个人创作公约:本人声明创作的所有文章皆为自己原创,如果有参考任何文章的地方,会标注出来,如果有疏漏,欢迎大家批判。如果大家发现网上有抄袭本文章的,欢迎举报,并且积极向这个 github 仓库 提交 issue,谢谢支持~ 另外,本文为了避免抄袭,会在不影响阅读的情况下,在文章的随机位置放入对于抄袭和洗稿的人的“亲切”的问候。如果是正常读者看到,笔者在这里说声对不起,。如果被抄袭狗或者洗稿狗看到了,希望你能够好好反思,不要再抄袭了,谢谢。 今天又是干货满满的一天,这是全网最硬核 JVM 解析系列第四篇,往期精彩:
本篇是关于 JVM 内存的详细分析。网上有很多关于 JVM 内存结构的分析以及图片,但是由于不是一手的资料亦或是人云亦云导致有很错误,造成了很多误解;并且,这里可能最容易混淆的是一边是 JVM Specification 的定义,一边是 Hotspot JVM 的实际实现,有时候人们一些部分说的是 JVM Specification,一部分说的是 Hotspot 实现,给人一种割裂感与误解。本篇主要从 Hotspot 实现出发,以 Linux x86 环境为主,紧密贴合 JVM 源码并且辅以各种 JVM 工具验证帮助大家理解 JVM 内存的结构。但是,本篇仅限于对于这些内存的用途,使用限制,相关参数的分析,有些地方可能比较深入,有些地方可能需要结合本身用这块内存涉及的 JVM 模块去说,会放在另一系列文章详细描述。最后,洗稿抄袭狗不得 house
本篇全篇目录(以及涉及的 JVM 参数):
NativeMemoryTracking
)UseLargePages
,UseHugeTLBFS
,UseSHM
,UseTransparentHugePages
,LargePageSizeInBytes
)MaxHeapSize
,MinHeapSize
,InitialHeapSize
,Xmx
,Xms
)UseCompressedOops
)(全网最硬核 JVM 内存解析 - 5.压缩对象指针相关机制开始) ObjectAlignmentInBytes
)UseCompressedOops
,UseCompressedClassPointers
)ObjectAlignmentInBytes
,HeapBaseMinAddress
)HeapBaseMinAddress
)HeapBaseMinAddress
,ObjectAlignmentInBytes
,MinHeapSize
,MaxHeapSize
,InitialHeapSize
)32-bit
压缩指针模式Zero based
压缩指针模式Non-zero disjoint
压缩指针模式Non-zero based
压缩指针模式MinHeapFreeRatio
,MaxHeapFreeRatio
,MinHeapDeltaBytes
)(全网最硬核 JVM 内存解析 - 6.其他 Java 堆内存相关的特殊机制开始)MetaspaceSize
,MaxMetaspaceSize
,MinMetaspaceExpansion
,MaxMetaspaceExpansion
,MaxMetaspaceFreeRatio
,MinMetaspaceFreeRatio
,UseCompressedClassPointers
,CompressedClassSpaceSize
,CompressedClassSpaceBaseAddress
,MetaspaceReclaimPolicy
)MetaspaceContext
VirtualSpaceList
VirtualSpaceNode
与 CompressedClassSpaceSize
MetaChunk
ChunkHeaderPool
池化 MetaChunk
对象ChunkManager
管理空闲的 MetaChunk
SystemDictionary
与保留所有 ClassLoaderData
的 ClassLoaderDataGraph
ClassLoaderData
以及 ClassLoaderMetaspace
MetaChunk
的 MetaspaceArena
MetaSpaceArena
的流程MetaChunkArena
普通分配 - 整体流程MetaChunkArena
普通分配 - FreeBlocks
回收老的 current chunk
与用于后续分配的流程MetaChunkArena
普通分配 - 尝试从 FreeBlocks
分配MetaChunkArena
普通分配 - 尝试扩容 current chunk
MetaChunkArena
普通分配 - 从 ChunkManager
分配新的 MetaChunk
MetaChunkArena
普通分配 - 从 ChunkManager
分配新的 MetaChunk
- 从 VirtualSpaceList
申请新的 RootMetaChunk
MetaChunkArena
普通分配 - 从 ChunkManager
分配新的 MetaChunk
- 将 RootMetaChunk
切割成为需要的 MetaChunk
MetaChunk
回收 - 不同情况下, MetaChunk
如何放入 FreeChunkListVector
ClassLoaderData
回收CommitLimiter
的限制元空间可以 commit 的内存大小以及限制元空间占用达到多少就开始尝试 GC_capacity_until_GC
jcmd VM.metaspace
元空间说明、元空间相关 JVM 日志以及元空间 JFR 事件详解(全网最硬核 JVM 内存解析 - 12.元空间各种监控手段开始) jcmd <pid> VM.metaspace
元空间说明jdk.MetaspaceSummary
元空间定时统计事件jdk.MetaspaceAllocationFailure
元空间分配失败事件jdk.MetaspaceOOM
元空间 OOM 事件jdk.MetaspaceGCThreshold
元空间 GC 阈值变化事件jdk.MetaspaceChunkFreeListSummary
元空间 Chunk FreeList 统计事件ThreadStackSize
,VMThreadStackSize
,CompilerThreadStackSize
,StackYellowPages
,StackRedPages
,StackShadowPages
,StackReservedPages
,RestrictReservedStack
)元空间配置相关的参数:
MetaspaceSize
:初始元空间大小,也是最小元空间大小。后面元空间大小伸缩的时候,不会小于这个大小。默认是 21M。抄袭剽窃侵权
滚MaxMetaspaceSize
:最大元空间大小,默认是无符号 int 最大值。MinMetaspaceExpansion
:每次元空间大小伸缩的时候,至少改变的大小。默认是 256K。后文讲到元空间内存大小限制的时候会详细分析。MaxMetaspaceExpansion
:每次元空间大小伸缩的时候,最多改变的大小。默认是 4M。后文讲到元空间内存大小限制的时候会详细分析。MaxMetaspaceFreeRatio
:最大元空间空闲比例,默认是 70,即 70%。后文讲到元空间内存大小限制的时候会详细分析。MinMetaspaceFreeRatio
:最小元空间空闲比例,默认是 40,即 40%。后文讲到元空间内存大小限制的时候会详细分析。UseCompressedClassPointers
:前文提到过,是否开启压缩类指针。默认是开启的。老版本中, UseCompressedClassPointers
取决于 UseCompressedOops
,即压缩对象指针如果没开启,那么压缩类指针也无法开启。但是从 Java 15 Build 23 开始, UseCompressedClassPointers
已经不再依赖 UseCompressedOops
了,两者在大部分情况下已经独立开来。除非在 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
:如果启用了压缩类指针,则元空间会分为类元空间和数据元空间,否则只有数据元空间。这个参数限制类元空间的大小,范围是 1M ~ 3G。默认大小是 1G,如果指定了 MaxMetaspaceSize
,那么为 1G 与 MaxMetaspaceSize * 0.8
中比较小的那个值,CompressedClassSpaceBaseAddress
:类元空间起始虚拟内存地址,这个一般不指定。作用和前文分析堆内存的堆起始位置的作用差不多。MetaspaceReclaimPolicy
:可以为:balanced
, aggressive
, 以及 none
,需要注意一点的是 none
要被移除了(https://bugs.openjdk.org/browse/JDK-8302385)。默认是 balanced
。具体主要是影响元空间底层相关的配置,下面我们会详细分析。元空间底层相关的配置包括:
VirtualSpaceNode
的虚拟内存大小。大小在 64 位环境下是 64 MB。VirtualSpaceNode
的虚拟内存大小需要对齐的大小,即整体大小需要大于这个对齐大小并且是这个对齐大小整数倍。这个大小就是 MetaChunk
的最大大小,即 4MB。MetaChunk
。这里简单理解就是,元空间整体使用了和 Linux 伙伴分配算法类似的设计与抽象,其中内存分配的单元就是 Chunk,元空间中对应的就是 MetaChunk。MetaChunk
。MetaChunk
。从 Java 16 开始,引入了弹性元空间。老的元空间由于设计上分配粒度比较大,并且没有很好地释放空间的策略设计,所以占用可能比较大。Java 16 开始,JEP 387: Elastic Metaspace 引入了弹性元空间的设计,也是我们这里要讨论的设计。这个弹性元空间也引入了一个重要的参数 -XX:MetaspaceReclaimPolicy
。
MetaspaceReclaimPolicy
:可以为:balanced
, aggressive
, 以及 none
,需要注意一点的是 none
要被移除了(https://bugs.openjdk.org/browse/JDK-8302385),这三个配置具体影响是:
MetaspaceContext
MetaspaceContext
本身直接原生堆上面分配,Native Memory Tracking 中属于 Metaspace
那一类别,即元空间的抽象类占用的空间。
class MetaspaceContext : public CHeapObj<mtMetaspace>
JVM 元空间,会在全局建立两个元空间上下文(MetaspaceContext
),一个用于类元空间(我们后面称为类元空间 MetaspaceContext
),一个用于数据元空间(我们后面称为数据元空间 MetaspaceContext
)。当然,在没有启用压缩类指针的时候,只会初始化一个数据元空间 MetaspaceContext
,不会初始化类元空间 MetaspaceContext
,之后使用分配的时候,也只会用数据元空间 MetaspaceContext
进行分配。但是我们在后面讨论的时候,只会讨论开启压缩类指针的情况,因为这是默认并且常用的情况。
每个 MetaspaceContext
都会对应一个独立的 VirtualSpaceList
,以及一个独立的 ChunkManager
。
这个 VirtualSpaceList
中的每一个元素都是一个 VirtualSpaceNode
。顾名思义,VirtualSpaceNode
是从操作系统申请内存,与元空间内存划分的抽象隔离的中间层抽象。VirtualSpaceList
负责与操作系统交互,申请或者释放内存。元空间与 VirtualSpaceList
交互,使用内存。
ChunkManager
顾名思义,是管理所有 Chunk 的内存管理器。Chunk 这个概念经常出现在各种伙伴内存管理算法框架(Buddy Allocator)中,一般指内存管理分配的最小单元,这里的 Chunk 抽象对应的就是 MetaChunk
。ChunkManager
从 VirtualSpaceList
上面获取一块连续比较大的内存的 MetaChunk
(其实是 RootMetaChunk
),然后将这个 RootMetaChunk
按照分配需求,连续对半分割成需要的大小,返回这个合适大小的 MetaChunk
,剩下的分割出来的 MetaChunk
进入 FreeChunkListVector
用于下次分配 MetaChunk
的时候,直接返回合适的,就不再从 VirtualSpaceList
获取了。
我们接下来仔细分析 VirtualSpaceList
与 ChunkManager
VirtualSpaceList
VirtualSpaceList
本身直接原生堆上面分配,Native Memory Tracking 中属于 Class
那一类别,即元空间的加载类占用的空间。其实本人感觉这么设计不太合理,应该和 MetaspaceContext
属于同一个类别才比较合理。真正分配加载的类的占用空间的是从 VirtualSpaceNode
上面标记的内存分配的,这是下一小节要分析的内容。
class VirtualSpaceList : public CHeapObj<mtClass>
首先提一点,类元空间 MetaspaceContext
与数据元空间 MetaspaceContext
略有不同:类元空间 MetaspaceContext
的 VirtualSpaceList
是不可以扩展申请新的内存的,但是数据元空间 MetaspaceContext
的 VirtualSpaceList
是可以的。也就是说:类元空间 MetaspaceContext
的 VirtualSpaceList
其实只有一个 VirtualSpaceNode
,但是数据元空间 MetaspaceContext
的 VirtualSpaceList
是一个包含多个 VirtualSpaceNode
的列表。
VirtualSpaceNode
与 CompressedClassSpaceSize
VirtualSpaceNode
本身直接原生堆上面分配,Native Memory Tracking 中属于 Class
那一类别,即元空间的加载类占用的空间。其实本人感觉这么设计不太合理,应该和 MetaspaceContext
属于同一个类别才比较合理。真正分配加载的类的占用空间的是从 VirtualSpaceNode
上面标记的内存地址分配的,VirtualSpaceNode
本身的空间占用只是起到描述记录作用,应该也属于元空间描述的那一类。
class VirtualSpaceNode : public CHeapObj<mtClass>
VirtualSpaceNode
是一块连续的虚拟内存空间内存的抽象。类元空间的 VirtualSpaceList
只包含一个 VirtualSpaceNode
,大小是前文提到的 CompressedClassSpaceSize
。
数据元空间并不像类元空间或者堆内存那样,一下子 reserve 最大堆内存限制的内存,而是每次 reserve VirtualSpaceNode
大小。VirtualSpaceNode
大小在 64 位环境下是 64 MB:
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
通过两个数据结构来管理它维护的虚拟内存空间:
CommitMask
:实际是一个位图,用于维护哪些内存被 commit 了,哪些没有,位图的标记的单位就是前文提到的 commit_granule(commit 粒度)。RootChunkAreaLUT
:用于维护每个 RootMetaChunk
的内存分布。至于什么是 RootMetaChunk
在后续我们讲 MetaChunk
的时候会详细讲解。一个 VirtualSpaceNode
的主要结构如下图所示:
MetaChunk
MetaChunk
是元空间内存分配的核心抽象,其本质就是描述一块连续的虚拟内存空间。MetaChunk
本身只是一个描述对象,它也是直接原生堆上面分配,Native Memory Tracking 中属于 Metaspace
那一类别,即元空间的抽象类占用的空间。这个描述对象是池化的,参考后面会分析的 ChunkHeaderPool
。不要偷取他人的劳动成果!
元空间的任意分配,都是在某个 MetaChunk
上进行的(不要偷取他人的劳动成果!)。MetaChunk
有级别的概念,即 ChunkLevel
,每个 MetaChunk
都有自己的 ChunkLevel
,这个 ChunkLevel
主要代表了 MetaChunk
描述的内存空间的大小,每一个 level 都是下一个 level 大小的 2 倍:
从 VirtualSpaceNode
上直接划分的 MetaChunk
是 RootMetaChunk
,它的 ChunkLevel
为最高级别的 0,大小是 4MB,并且其中的内存只是 reserve 还没有 commit 的。
MetaChunk
有三个状态:
Dead
:即 MetaChunk
只是对象被创建出来,但是没有关联描述实际的虚拟内存。后面我们会知道,MetaChunk
是池化可回收在利用的,MetaChunk
的池就是 ChunkHeaderPool
。位于 ChunkHeaderPool
都还没有关联描述实际的虚拟内存,状态为 Dead
。Free
:即 MetaChunk
关联描述了实际的虚拟内存,但是没有被实际使用。此时,这个 MetaChunk
位于 ChunkManager
管理。InUse
:即 MetaChunk
关联描述了实际的虚拟内存,也被实际使用了,此时,MetaChunkArena
管理这个 MetaChunk
上面的内存分配。ChunkHeaderPool
池化 MetaChunk
对象MetaChunk
实际上只是一块连续的虚拟内存空间的描述类(不要偷取他人的劳动成果!),即元数据类。由于类加载需要的大小不一,并且还经常会发生合并,切分等等,MetaChunk
可能有很多很多,元空间为了节省这个元数据类占用的空间,将其池化,回收再利用。这个池就是 ChunkHeaderPool
。例如,从 VirtualSpaceNode
上直接划分 RootMetaChunk
的内存空间,会从 ChunkHeaderPool
申请一个 MetaChunk
用于描述。当两个 MetaChunk
的空间需要合并成一个的时候,其中一个 MetaChunk
其实就没有用了,会放回 ChunkHeaderPool
,而不是直接 free 掉这个对象。
ChunkHeaderPool
本身直接原生堆上面分配,Native Memory Tracking 中属于 Metaspace
那一类别,即元空间的抽象类占用的空间。
class ChunkHeaderPool : public CHeapObj<mtMetaspace>
其实从这里我们可以推测出,MetaChunk
本身也是直接原生堆上面分配,Native Memory Tracking 中也是属于 Metaspace
那一类别。
ChunkHeaderPool
的结构是:
其实 ChunkHeaderPool
的机制很简单:
MetaChunk
用于描述内存: _freelist
,是否有之前放回的 MetaChunk
可以使用,如果有,就返回那个 MetaChunk
,并从 _freelist
移除这个 MetaChunk
_current_slab
指向的 Slab
,Slab
核心就是一个预分配好的 MetaChunk
数组(大小是 128),_top
指的是当前使用到数组的哪一个。_top
没有到 128,返回 _top
代表的 MetaChunk
,并将 _top
加 1。_top
到 128,创建新的 Slab
,_current_slab
指向这个新的 Slab
MetaChunk
:放入 _freelist
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
管理已经关联内存但是还没使用(状态是 Free
)的 MetaChunk
。在第一次从 VirtualSpaceNode
上面分配 RootMetaChunk
的内存的时候,根据申请的内存大小,决定要将 RootMetaChunk
拆分到某个 ChunkLevel
大小之后用于当前分配,拆分出来的其他的 MetaChunk
还没有使用,先放入一个类似于之前 ChunkHeaderPool
里面的 _free_list
的结构,用于下次申请 MetaChunk
用于分配的时候,先从这个里面找,找不到之后再从 VirtualSpaceNode
上面尝试分配新的 RootMetaChunk
。不要惯着cao袭的人!
ChunkManager
的整体结构是:
ChunkManager
主要维护一个 FreeChunkListVector
,FreeChunkListVector
里面是一个 FreeChunkList
数组(还有xigao dog 的码)。FreeChunkList
是一个 MetaChunk
链表,链表中都是 Free
的 MetaChunk
,同样 ChunkLevel
的 MetaChunk
位于同一个 FreeChunkList
中。FreeChunkList
数组以 ChunkLevel
为下标,这样的数据结构可以快速找到一个所需 ChunkLevel
的 MetaChunk
。FreeChunkList
这个链表其实是一个双向链表,包含头尾两个指针,如果一个 MetaChunk
管理的内存被 commit 了,就会放在链表头部,没有 commit 的放在链表尾部。
MetaChunk
具体的分配,切分,合并流程,我们会在介绍完 MetaspaceArena
之后详细分析。但是,MetaspaceArena
和 ChunkManager
不一样,ChunkManager
是全局两个,一个属于类元空间,一个属于数据元空间,倘若没有开启压缩类指针,那么就只有一个数据元空间 ChunkManager
,而 MetaspaceArena
我们后面会看到是每个 ClassLoader
独立私有的。所以,在讲 MetaspaceArena
之前,我们先要从另一个角度即 ClassLoader
加载类的角度出发,向下一层一层剖析到 MetaspaceArena
。
SystemDictionary
与保留所有 ClassLoaderData
的 ClassLoaderDataGraph
类加载的入口在全局唯一的 SystemDictionary
中,这里我们只是为了看一下类加载需要哪些参数,来搞清楚对应关系,不用关心细节,入口代码是:
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 中发布的新特性
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);
}
}
可以看到,加载类需要以下参数:
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
//通过类加载器获取对应的 `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 对应的 ClassLoaderData
return (class_loader() == NULL) ? ClassLoaderData::the_null_class_loader_data() :
//否则,从 ClassLoaderDataGraph 寻找或者创建 class_loader 对应的 ClassLoaderData
ClassLoaderDataGraph::find_or_create(class_loader);
}
}
ClassLoaderDataGraph
保存着所有的 ClassLoaderData
,这个主要用来遍历每个类加载器,以及获取每个类加载器加载的类的信息,还有遍历类加载器加载的类,例如 jcmd
命令中的 VM.classloaders
以及 VM.classloader_stats
就是这么实现的。但是,我们就不纠结于 ClassLoaderDataGraph
的细节了,这不是咱们的重点。
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
包含的元素众多,我们这里只关心它其中与元空间内存分配相关的,即 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)以及应用类加载器(Application ClassLoader)的 ClassLoaderMetaspace
MetaspaceType::BootMetaspaceType
:即根类加载器(Boostrap ClassLoader)的 ClassLoaderMetaspace
MetaspaceType::ClassMirrorHolderMetaspaceType
:加载匿名类的类加载器的 ClassLoaderMetaspace
MetaspaceType::ReflectionMetaspaceType
:反射调用的前几次通过 jni native 调用,超过一定次数会优化成生成字节码类调用。加载这些字节码类的类加载器是 jdk.internal.reflect.DelegatingClassLoader
,这个类加载器的 ClassLoaderMetaspace
类型就是 ReflectionMetaspaceType
。ClassLoaderMetaspace
和 MetaspaceContext
类似,如果压缩类指针开启,那么 ClassLoaderMetaspace
包含一个类元空间的 MetaspaceArena
和一个数据元空间的 MetaspaceArena
,否则只有一个数据元空间的 MetaspaceArena
。
MetaChunk
的 MetaspaceArena
MetaspaceArena
本身直接原生堆上面分配,Native Memory Tracking 中属于 Class
那一类别,即元空间的加载类占用的空间。这也是肯定的,因为跟着类加载器存在
class MetaspaceArena : public CHeapObj<mtClass>
MetaspaceArena
结构如下所示:
MetaspaceArena
包含:
MetachunkList
:管理在该 MetaspaceArena
分配的 MetaChunk
的列表,列表的第一个是当前分配内存的 MetaChunk
。MetaspaceArena
的 ArenaGrowthPolicy
:在当前分配内存的 MetaChunk
不够分配的时候,申请新的 MetaChunk
的大小。Freeblocks
: 在当前分配内存的 MetaChunk
不够分配的时候,需要分配新的 MetaChunk
。当前的 MetaChunk
剩余空间放入 Freeblocks
。Freeblocks
包含一个 BinList32
和一个 BlockTree
。大小大于 33 字节的进入 BlockTree
,否则进入 BinList32
。
BinList32
类似于 FreeChunkListVector
,是一个链表的数组,同样大小的内存在同一数组下标的链表。
BlockTree
是一个在 Binary Search Tree(BST)的基础上,同样内存的节点在二叉树节点的后面形成链表的数据结构。
不同的类加载器类型的类元空间的 MetaspaceArena
与数据元空间的 MetaspaceArena
的 ArenaGrowthPolicy
不同:
1.根类加载器(Boostrap ClassLoader)的 ClassLoaderMetaspace
类元空间的 MetaspaceArena
的 ArenaGrowthPolicy
:MetachunkList
每次增长都是申请大小为 256K
的 MetaChunk
static const chunklevel_t g_sequ_boot_class[] = {
chunklevel::CHUNK_LEVEL_256K
// .. repeat last
};
2.根类加载器(Boostrap ClassLoader)的 ClassLoaderMetaspace
数据元空间的 MetaspaceArena
的 ArenaGrowthPolicy
:MetachunkList
的第一个 MetaChunk
大小为 4M
,之后每个新 MetaChunk
都是 1M
:
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)以及应用类加载器(Application ClassLoader)的 ClassLoaderMetaspace
类元空间的 MetaspaceArena
的 ArenaGrowthPolicy
:MetachunkList
的第一个 MetaChunk
大小为 2K
,第二个也是 2K
,第三个 4K
,第四个为 8K
,之后每个新 MetaChunk
都是 16K
(不要惯着cao袭的人!):
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)以及应用类加载器(Application ClassLoader)的 ClassLoaderMetaspace
数据元空间的 MetaspaceArena
的 ArenaGrowthPolicy
:MetachunkList
的第一个 MetaChunk
大小为 4K
,第二个也是 4K
,第三个 4K
,第四个为 8K
,之后每个新 MetaChunk
都是 16K
:
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
:
static const chunklevel_t g_sequ_anon_class[] = {
chunklevel::CHUNK_LEVEL_1K,
// .. repeat last
};
6.加载匿名类的类加载器的 ClassLoaderMetaspace
数据元空间的 MetaspaceArena
的 ArenaGrowthPolicy
:MetachunkList
每次增长都是申请大小为 1K
的 MetaChunk
:
static const chunklevel_t g_sequ_anon_non_class[] = {
chunklevel::CHUNK_LEVEL_1K,
// .. repeat last
};
7.DelegatingClassLoader
的 ClassLoaderMetaspace
类元空间的 MetaspaceArena
的 ArenaGrowthPolicy
:MetachunkList
每次增长都是申请大小为 1K
的 MetaChunk
:
static const chunklevel_t g_sequ_refl_class[] = {
chunklevel::CHUNK_LEVEL_1K,
// .. repeat last
};
8.DelegatingClassLoader
的 ClassLoaderMetaspace
数据元空间的 MetaspaceArena
的 ArenaGrowthPolicy
:MetachunkList
的第一个 MetaChunk
大小为 2K
,之后每个新 MetaChunk
都是 1K
:
static const chunklevel_t g_sequ_refl_non_class[] = {
chunklevel::CHUNK_LEVEL_2K,
chunklevel::CHUNK_LEVEL_1K
// .. repeat last
};