个人创作公约:本人声明创作的所有文章皆为自己原创,如果有参考任何文章的地方,会标注出来,如果有疏漏,欢迎大家批判。如果大家发现网上有抄袭本文章的,欢迎举报,并且积极向这个 github 仓库 提交 issue,谢谢支持~另外,本文为了避免抄袭,会在不影响阅读的情况下,在文章的随机位置放入对于抄袭和洗稿的人的“亲切”的问候。如果是正常读者看到,笔者在这里说声对不起,。如果被抄袭狗或者洗稿狗看到了,希望你能够好好反思,不要再抄袭了,谢谢。今天又是干货满满的一天,这是全网最硬核 JVM 解析系列第四篇,往期精彩:
全网最硬核 TLAB 解析
(相关资料图)
全网最硬核 Java 随机数解析
全网最硬核 Java 新内存模型解析
本篇是关于 JVM 内存的详细分析。网上有很多关于 JVM 内存结构的分析以及图片,但是由于不是一手的资料亦或是人云亦云导致有很错误,造成了很多误解;并且,这里可能最容易混淆的是一边是 JVM Specification 的定义,一边是 Hotspot JVM 的实际实现,有时候人们一些部分说的是 JVM Specification,一部分说的是 Hotspot 实现,给人一种割裂感与误解。本篇主要从 Hotspot 实现出发,以 Linux x86 环境为主,紧密贴合 JVM 源码并且辅以各种 JVM 工具验证帮助大家理解 JVM 内存的结构。但是,本篇仅限于对于这些内存的用途,使用限制,相关参数的分析,有些地方可能比较深入,有些地方可能需要结合本身用这块内存涉及的 JVM 模块去说,会放在另一系列文章详细描述。最后,洗稿抄袭狗不得 house
本篇全篇目录(以及涉及的 JVM 参数):
从 Native Memory Tracking 说起(全网最硬核 JVM 内存解析 - 1.从 Native Memory Tracking 说起开始)
Native Memory Tracking 的开启
Native Memory Tracking 的使用(涉及 JVM 参数:NativeMemoryTracking
)
Native Memory Tracking 的 summary 信息每部分含义
Native Memory Tracking 的 summary 信息的持续监控
为何 Native Memory Tracking 中申请的内存分为 reserved 和 committed
JVM 内存申请与使用流程(全网最硬核 JVM 内存解析 - 2.JVM 内存申请与使用流程开始)
Linux 大页分配方式 - Huge Translation Lookaside Buffer Page (hugetlbfs)
Linux 大页分配方式 - Transparent Huge Pages (THP)
JVM 大页分配相关参数与机制(涉及 JVM 参数:UseLargePages
,UseHugeTLBFS
,UseSHM
,UseTransparentHugePages
,LargePageSizeInBytes
)
JVM commit 的内存与实际占用内存的差异
Linux 下内存管理模型简述
JVM commit 的内存与实际占用内存的差异
大页分配 UseLargePages(全网最硬核 JVM 内存解析 - 3.大页分配 UseLargePages开始)
Java 堆内存相关设计(全网最硬核 JVM 内存解析 - 4.Java 堆内存大小的确认开始)
验证 32-bit
压缩指针模式
验证 Zero based
压缩指针模式
验证 Non-zero disjoint
压缩指针模式
验证 Non-zero based
压缩指针模式
压缩对象指针存在的意义(涉及 JVM 参数:ObjectAlignmentInBytes
)
压缩对象指针与压缩类指针的关系演进(涉及 JVM 参数:UseCompressedOops
,UseCompressedClassPointers
)
压缩对象指针的不同模式与寻址优化机制(涉及 JVM 参数:ObjectAlignmentInBytes
,HeapBaseMinAddress
)
通用初始化与扩展流程
直接指定三个指标的方式(涉及 JVM 参数:MaxHeapSize
,MinHeapSize
,InitialHeapSize
,Xmx
,Xms
)
不手动指定三个指标的情况下,这三个指标(MinHeapSize,MaxHeapSize,InitialHeapSize)是如何计算的
压缩对象指针相关机制(涉及 JVM 参数:UseCompressedOops
)(全网最硬核 JVM 内存解析 - 5.压缩对象指针相关机制开始)
为何预留第 0 页,压缩对象指针 null 判断擦除的实现(涉及 JVM 参数:HeapBaseMinAddress
)
结合压缩对象指针与前面提到的堆内存限制的初始化的关系(涉及 JVM 参数:HeapBaseMinAddress
,ObjectAlignmentInBytes
,MinHeapSize
,MaxHeapSize
,InitialHeapSize
)
使用 jol + jhsdb + JVM 日志查看压缩对象指针与 Java 堆验证我们前面的结论
堆大小的动态伸缩(涉及 JVM 参数:MinHeapFreeRatio
,MaxHeapFreeRatio
,MinHeapDeltaBytes
)(全网最硬核 JVM 内存解析 - 6.其他 Java 堆内存相关的特殊机制开始)
适用于长期运行并且尽量将所有可用内存被堆使用的 JVM 参数 AggressiveHeap
JVM 参数 AlwaysPreTouch 的作用
JVM 参数 UseContainerSupport - JVM 如何感知到容器内存限制
JVM 参数 SoftMaxHeapSize - 用于平滑迁移更耗内存的 GC 使用
JVM 元空间设计(全网最硬核 JVM 内存解析 - 7.元空间存储的元数据开始)
jcmd <pid> VM.metaspace
元空间说明
元空间相关 JVM 日志
元空间 JFR 事件详解
jdk.MetaspaceSummary
元空间定时统计事件
jdk.MetaspaceAllocationFailure
元空间分配失败事件
jdk.MetaspaceOOM
元空间 OOM 事件
jdk.MetaspaceGCThreshold
元空间 GC 阈值变化事件
jdk.MetaspaceChunkFreeListSummary
元空间 Chunk FreeList 统计事件
CommitLimiter
的限制元空间可以 commit 的内存大小以及限制元空间占用达到多少就开始尝试 GC
每次 GC 之后,也会尝试重新计算 _capacity_until_GC
首先类加载器 1 需要分配 1023 字节大小的内存,属于类空间
然后类加载器 1 还需要分配 1023 字节大小的内存,属于类空间
然后类加载器 1 需要分配 264 KB 大小的内存,属于类空间
然后类加载器 1 需要分配 2 MB 大小的内存,属于类空间
然后类加载器 1 需要分配 128KB 大小的内存,属于类空间
新来一个类加载器 2,需要分配 1023 Bytes 大小的内存,属于类空间
然后类加载器 1 被 GC 回收掉
然后类加载器 2 需要分配 1 MB 大小的内存,属于类空间
元空间的整体配置以及相关参数(涉及 JVM 参数:MetaspaceSize
,MaxMetaspaceSize
,MinMetaspaceExpansion
,MaxMetaspaceExpansion
,MaxMetaspaceFreeRatio
,MinMetaspaceFreeRatio
,UseCompressedClassPointers
,CompressedClassSpaceSize
,CompressedClassSpaceBaseAddress
,MetaspaceReclaimPolicy
)
元空间上下文 MetaspaceContext
虚拟内存空间节点列表 VirtualSpaceList
虚拟内存空间节点 VirtualSpaceNode
与 CompressedClassSpaceSize
MetaChunk
类加载的入口 SystemDictionary
与保留所有 ClassLoaderData
的 ClassLoaderDataGraph
每个类加载器私有的 ClassLoaderData
以及 ClassLoaderMetaspace
管理正在使用的 MetaChunk
的 MetaspaceArena
元空间内存分配流程(全网最硬核 JVM 内存解析 - 9.元空间内存分配流程开始)
ClassLoaderData
回收
ChunkHeaderPool
池化 MetaChunk
对象
ChunkManager
管理空闲的 MetaChunk
类加载器到 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
什么时候用到元空间,以及释放时机
元空间保存什么
什么是元数据,为什么需要元数据
什么时候用到元空间,元空间保存什么
元空间的核心概念与设计(全网最硬核 JVM 内存解析 - 8.元空间的核心概念与设计开始)
元空间分配与回收流程举例(全网最硬核 JVM 内存解析 - 10.元空间分配与回收流程举例开始)
元空间大小限制与动态伸缩(全网最硬核 JVM 内存解析 - 11.元空间分配与回收流程举例开始)
jcmd VM.metaspace
元空间说明、元空间相关 JVM 日志以及元空间 JFR 事件详解(全网最硬核 JVM 内存解析 - 12.元空间各种监控手段开始)
JVM 线程内存设计(重点研究 Java 线程)(全网最硬核 JVM 内存解析 - 13.JVM 线程内存设计开始)
解释执行与编译执行时候的判断(x86为例)
一个 Java 线程 Xss 最小能指定多大
JVM 中有哪几种线程,对应线程栈相关的参数是什么(涉及 JVM 参数:ThreadStackSize
,VMThreadStackSize
,CompilerThreadStackSize
,StackYellowPages
,StackRedPages
,StackShadowPages
,StackReservedPages
,RestrictReservedStack
)
Java 线程栈内存的结构
Java 线程如何抛出的 StackOverflowError
3. Java 堆内存相关设计
3.1. 通用初始化与扩展流程
目前最新的 JVM,主要根据三个指标初始化堆以及扩展或缩小堆:
最大堆大小
最小堆大小
初始堆大小
不同的 GC 情况下,初始化以及扩展的流程可能在某些细节不太一样,但是,大体的思路都是:
初始化阶段,reserve 最大堆大小,并且 commit 初始堆大小
在某些 GC 的某些阶段,根据上次 GC 的数据,动态扩展或者缩小堆大小,扩展就是 commit 更多,缩小就是 uncommit 一部分内存。但是,堆大小不会小于最小堆大小,也不会大于最大堆大小
3.2. 直接指定三个指标(MinHeapSize,MaxHeapSize,InitialHeapSize)的方式
这三个指标,直接对应的 JVM 参数是:
最大堆大小:MaxHeapSize
,如果没有指定的话会有默认预设值用于指导 JVM 计算这些指标的大小,下一章节会详细分析,预设值为 125MB 左右(96M*13/10)
最小堆大小:MinHeapSize
,默认为 0,0 代表让 JVM 自己计算,下一章节会详细分析
初始堆大小:InitialHeapSize
,默认为 0,0 代表让 JVM 自己计算,下一章节会详细分析
对应源码是:https://github.com/openjdk/jdk/blob/jdk-21+3/src/hotspot/share/gc/shared/gc_globals.hpp
:
#define ScaleForWordSize(x) align_down((x) * 13 / 10, HeapWordSize)product(size_t, MaxHeapSize, ScaleForWordSize(96*M), \ "Maximum heap size (in bytes)") \ constraint(MaxHeapSizeConstraintFunc,AfterErgo) \product(size_t, MinHeapSize, 0, \ "Minimum heap size (in bytes); zero means use ergonomics") \ constraint(MinHeapSizeConstraintFunc,AfterErgo) \product(size_t, InitialHeapSize, 0, \ "Initial heap size (in bytes); zero means use ergonomics") \ constraint(InitialHeapSizeConstraintFunc,AfterErgo) \
我们可以通过类似于 -XX:MaxHeapSize=1G
这种启动参数对这三个指标进行设置,但是,我们经常看到的可能是 Xmx
以及 Xms
这两个参数设置这三个指标,这两个参数分别对应:
Xmx
:对应 最大堆大小等价于 MaxHeapSize
Xms
:相当于同时设置最小堆大小MinHeapSize
和初始堆大小InitialHeapSize
对应的 JVM 源码是:https://github.com/openjdk/jdk/blob/jdk-21+3/src/hotspot/share/runtime/arguments.cpp
:
//如果设置了 Xmselse if (match_option(option, "-Xms", &tail)) { julong size = 0; //解析 Xms 大小 ArgsRange errcode = parse_memory_size(tail, &size, 0); if (errcode != arg_in_range) { jio_fprintf(defaultStream::error_stream(), "Invalid initial heap size: %s\n", option->optionString); describe_range_error(errcode); return JNI_EINVAL; } //将解析的值设置到 MinHeapSize if (FLAG_SET_CMDLINE(MinHeapSize, (size_t)size) != JVMFlag::SUCCESS) { return JNI_EINVAL; } //将解析的值设置到 InitialHeapSize if (FLAG_SET_CMDLINE(InitialHeapSize, (size_t)size) != JVMFlag::SUCCESS) { return JNI_EINVAL; }//如果设置了 Xmx} else if (match_option(option, "-Xmx", &tail) || match_option(option, "-XX:MaxHeapSize=", &tail)) { julong long_max_heap_size = 0; //解析 Xmx 大小 ArgsRange errcode = parse_memory_size(tail, &long_max_heap_size, 1); if (errcode != arg_in_range) { jio_fprintf(defaultStream::error_stream(), "Invalid maximum heap size: %s\n", option->optionString); describe_range_error(errcode); return JNI_EINVAL; } //将解析的值设置到 MaxHeapSize if (FLAG_SET_CMDLINE(MaxHeapSize, (size_t)long_max_heap_size) != JVMFlag::SUCCESS) { return JNI_EINVAL; }}
最后提一句,JVM 启动参数,同一个参数可以多次出现,但是只有最后一个会生效,例如:
java -XX:MaxHeapSize=8G -XX:MaxHeapSize=4G -XX:MaxHeapSize=8M -version
这个命令启动的 JVM MaxHeapSize 为 8MB。由于前面提到 Xmx 与 MaxHeapSize 是等价的,所以这么写也是可以的(虽然最后 MaxHeapSize 还是 8MB):
java -Xmx=8G -XX:MaxHeapSize=4G -XX:MaxHeapSize=8M -version
3.3. 不手动指定三个指标的情况下,这三个指标(MinHeapSize,MaxHeapSize,InitialHeapSize)是如何计算的
上一章节我们提到我们可以手动指定这三个参数,如果不指定呢?JVM 会怎么计算这三个指标的大小?首先,当然,JVM 会读取 JVM 可用内存:首先 JVM 需要知道自己可用多少内存,我们称为可用内存。由此引入第一个 JVM 参数,MaxRAM
,这个参数是用来明确指定 JVM 进程可用内存大小的,如果没有指定,JVM 会自己读取系统可用内存。这个可用内存用来指导 JVM 限制最大堆内存。后面我们会看到很多 JVM 参数与这个可用内存相关。
前面我们还提到了,就算没有指定 MaxHeapSize
或者 Xmx
,MaxHeapSize
也有自己预设的一个参考值。源码中这个预设参考值为 125MB 左右(96M*13/10
)。但是一般最后不会以这个参考值为准,JVM 初始化的时候会有很复杂的计算计算出合适的值。比如你可以在你的电脑上执行下下面的命令,可以看到类似下面的输出:
> java -XX:+UnlockDiagnosticVMOptions -XX:+PrintFlagsFinal -version|grep MaxHeapSize size_t MaxHeapSize = 1572864000 {product} {ergonomic} size_t SoftMaxHeapSize = 1572864000 {manageable} {ergonomic}openjdk version "17.0.2" 2022-01-18 LTSOpenJDK Runtime Environment Corretto-17.0.2.8.1 (build 17.0.2+8-LTS)OpenJDK 64-Bit Server VM Corretto-17.0.2.8.1 (build 17.0.2+8-LTS, mixed mode, sharing)
可以看到 MaxHeapSize
的大小,以及它的值是通过 ergonomic 决定的。也就是非人工指定而是 JVM 自己算出来的。
上面提到的那个 125MB 左右的初始参考值,一般用于 JVM 计算。我们接下来就会分析这个计算流程,首先是最大堆内存 MaxHeapSize 的计算流程:
流程中涉及了以下几个参数,还有一些已经过期的参数,会被转换成未过期的参数:
MinRAMPercentage
:注意不要被名字迷惑,这个参数是在可用内存比较小的时候生效,即最大堆内存占用为可用内存的这个参数指定的百分比,默认为 50,即 50%
MaxRAMPercentage
:注意不要被名字迷惑,这个参数是在可用内存比较大的时候生效,即最大堆内存占用为可用内存的这个参数指定的百分比,默认为 25,即 25%
ErgoHeapSizeLimit
:通过自动计算,计算出的最大堆内存大小不超过这个参数指定的大小,默认为 0 即不限制
MinRAMFraction
: 已过期,如果配置了会转化为 MinRAMPercentage
换算关系是:MinRAMPercentage
= 100.0 / MinRAMFraction
,默认是 2
MaxRAMFraction
: 已过期,如果配置了会转化为 MaxRAMPercentage
换算关系是:MaxRAMPercentage
= 100.0 / MaxRAMFraction
,默认是 4
对应的源码是:https://github.com/openjdk/jdk/blob/jdk-21+3/src/hotspot/share/gc/shared/gc_globals.hpp
:
product(double, MinRAMPercentage, 50.0, \ "Minimum percentage of real memory used for maximum heap" \ "size on systems with small physical memory size") \ range(0.0, 100.0) \product(double, MaxRAMPercentage, 25.0, \ "Maximum percentage of real memory used for maximum heap size") \ range(0.0, 100.0) \product(size_t, ErgoHeapSizeLimit, 0, \ "Maximum ergonomically set heap size (in bytes); zero means use " \ "MaxRAM * MaxRAMPercentage / 100") \ range(0, max_uintx) \product(uintx, MinRAMFraction, 2, \ "Minimum fraction (1/n) of real memory used for maximum heap " \ "size on systems with small physical memory size. " \ "Deprecated, use MinRAMPercentage instead") \ range(1, max_uintx) \product(uintx, MaxRAMFraction, 4, \ "Maximum fraction (1/n) of real memory used for maximum heap " \ "size. " \ "Deprecated, use MaxRAMPercentage instead") \ range(1, max_uintx) \
然后如果我们也没有设置 MinHeapSize
以及 InitialHeapSize
,也会经过下面的计算过程计算出来:
流程中涉及了以下几个参数,还有一些已经过期的参数,会被转换成未过期的参数:
NewSize
:初始新生代大小,预设值为 1.3MB 左右(1*13/10
)
OldSize
:老年代大小,预设值为 5.2 MB 左右(4*13/10
)
InitialRAMPercentage
:初始堆内存为可用内存的这个参数指定的百分比,默认为 1.5625,即 1.5625%
InitialRAMFraction
: 已过期,如果配置了会转化为 InitialRAMPercentage
换算关系是:InitialRAMPercentage
= 100.0 / InitialRAMFraction
对应的源码是:https://github.com/openjdk/jdk/blob/jdk-21+3/src/hotspot/share/gc/shared/gc_globals.hpp
:
product(size_t, NewSize, ScaleForWordSize(1*M), \ "Initial new generation size (in bytes)") \ constraint(NewSizeConstraintFunc,AfterErgo) \product(size_t, OldSize, ScaleForWordSize(4*M), \ "Initial tenured generation size (in bytes)") \ range(0, max_uintx) \product(double, InitialRAMPercentage, 1.5625, \ "Percentage of real memory used for initial heap size") \ range(0.0, 100.0) \product(uintx, InitialRAMFraction, 64, \ "Fraction (1/n) of real memory used for initial heap size. " \ "Deprecated, use InitialRAMPercentage instead") \ range(1, max_uintx) \
微信搜索“干货满满张哈希”关注公众号,加作者微信,每日一刷,轻松提升技术,斩获各种offer我会经常发一些很好的各种框架的官方社区的新闻视频资料并加上个人翻译字幕到如下地址(也包括上面的公众号),欢迎关注:
知乎:https://www.zhihu.com/people/zhxhash
B 站:https://space.bilibili.com/31359187
X 关闭
- 1、全网最硬核 JVM 内存解析 - 4.Java 堆内存大小的确认
- 2、现代大学治理及其功能研究_关于现代大学治理及其功能研究介绍 环球热文
- 3、华侨城A:公司于2023年4月21日召开2022年年度股东大会,会议由公司副董事长刘凤喜主持_热点评
- 4、4月26日晚间上市公司利好消息一览(附名单)
- 5、北京西城小学全区招生学校增至12所
- 6、热缩片是什么材料做的(RSP是什么意思)
- 7、亲水行丨亲水踏青可以有!16区美丽幸福河湖打卡点,邀你共赴水之旅~金山区水库中心河篇|重点聚焦
- 8、我国网络通信产业四大集群分布,深京沪领跑
- 9、世界要闻:君乐宝集团华南液态奶基地项目落户广东江门
- 10、聚焦高质量发展|融资融智护航企业“出海” 广东金融业打出广交会服务“组合拳” 天天热点评
-
【独家】暗夜学院:诱惑_对于暗夜学院:诱惑简单介绍
1、《暗夜学院6:诱惑》内容简介:《暗夜学院6:诱惑》是作者精心编写而成。2、《暗夜学院6:诱惑》是“暗夜学
-
河北外国语学院国际处招聘信息|世界滚动
一、学校简介河北外国语学院20多年来已发展成为开设75个语种,142个本专科专业的国际化综合性的外国语大学
-
微软 690 亿美元收购动视暴雪有望于本周获批 新视野
当地时间本周二,消息人士透露称,微软本周对获得英国批准其收购感到乐观,这是因为最近几周,英国和欧盟的
-
热议:桥梁包干合同范本(推荐25篇)
桥梁包干合同范本第1篇我所从事的单位是xx路桥桥梁工程公司,被分到了xx大桥项目部,我所从事的工作是技术
-
世界热门:一棵在荒野中站立的树——读《教师的书桌:从阅读抵达教学》
我们一般不叫她“周春梅”,总是习惯于称她“仙女”。用“仙女”称呼她,似乎再合适不过了。她不喜欢无聊的
-
环球热点!翰墨飘香读书节 丰富校园文化生活
4月19号上午,伊宁县愉群翁回族乡中心小学举行“书香浸润童年,阅读点亮人生”第六届校园文化读书节活动,
-
乌鲁木齐市专精特新中小企业“跑出”竞争力
乌鲁木齐市专精特新中小企业“跑出”竞争力,中小企业,乌鲁木齐市,制造业
-
安全环保月工作计划表(实用15篇) 世界观焦点
安全环保月工作计划表第1篇一、现状分析与指导思想上学期,我们按计划完成了各项保育工作,保育员教师的工
-
【世界播资讯】COPENHAGEN时装秀,By Malene Birger 2023秋冬系列,秀场解析
COPENHAGEN时装秀,ByMaleneBirger2023秋冬系列的这些服装在纯色颜色的简约大方中,还有宽松廓形带来的休闲
-
助力“保交楼” 东西湖区行政审批局开展联合验收服务|天天观天下
助力“保交楼”东西湖区行政审批局开展联合验收服务---湖北日报讯(记者成熔兴、通讯员易斌)“东西湖区实