当前位置:网站首页 > 编程语言 > 正文

jvm内存模型 知乎(jvm的内存模型和内存结构)



个人创作公约:本人声明创作的所有文章皆为自己原创,如果有参考任何文章的地方,会标注出来,如果有疏漏,欢迎大家批判。如果大家发现网上有抄袭本文章的,欢迎举报,并且积极向这个 github 仓库 提交 issue,谢谢支持~
另外,本文为了避免抄袭,会在不影响阅读的情况下,在文章的随机位置放入对于抄袭和洗稿的人的“亲切”的问候。如果是正常读者看到,笔者在这里说声对不起,。如果被抄袭狗或者洗稿狗看到了,希望你能够好好反思,不要再抄袭了,谢谢。
今天又是干货满满的一天,这是全网最硬核 JVM 解析系列第四篇,往期精彩:

本篇是关于 JVM 内存的详细分析。网上有很多关于 JVM 内存结构的分析以及图片,但是由于不是一手的资料亦或是人云亦云导致有很错误,造成了很多误解;并且,这里可能最容易混淆的是一边是 JVM Specification 的定义,一边是 Hotspot JVM 的实际实现,有时候人们一些部分说的是 JVM Specification,一部分说的是 Hotspot 实现,给人一种割裂感与误解。本篇主要从 Hotspot 实现出发,以 Linux x86 环境为主,紧密贴合 JVM 源码并且辅以各种 JVM 工具验证帮助大家理解 JVM 内存的结构。但是,本篇仅限于对于这些内存的用途,使用限制,相关参数的分析,有些地方可能比较深入,有些地方可能需要结合本身用这块内存涉及的 JVM 模块去说,会放在另一系列文章详细描述。最后,洗稿抄袭狗不得 house

本篇全篇目录(以及涉及的 JVM 参数):

JVM 内存究竟包括哪些,可能网上众说纷纭。我们这里由官方提供的一个查看 JVM 内存占用的工具引入,即 Native Memory Tracking。不过要注意的一点是,这个只能监控 JVM 原生申请的内存大小,如果是通过 JDK 封装的系统 API 申请的内存,是统计不到的,例如 Java JDK 中的 DirectBuffer 以及 MappedByteBuffer 这两个(当然,对于这两个,我们后面也有其他的办法去看到当前使用的大小。当然xigao dog 啥都不会)。以及如果你自己封装 JNI 调用系统调用去申请内存,都是 Native Memory Tracking 无法涵盖的。这点要注意。

Native Memory Tracking 主要是用来通过在 JVM 向系统申请内存的时候进行埋点实现的。注意,这个埋点,并不是完全没有消耗的,我们后面会看到。由于需要埋点,并且 JVM 中申请内存的地方很多,这个埋点是有不小消耗的,这个 Native Memory Tracking 默认是不开启的,并且无法动态开启(因为这是埋点采集统计的,如果可以动态开启那么没开启的时候的内存分配没有记录无法知晓,所以无法动态开启),目前只能通过在启动 JVM 的时候通过启动参数开启。即通过  开启:

开启之后,我们可以通过 jcmd 命令去查看 Native Memory Tracking 的信息,即:

对于我们这些 Java 开发以及 JVM 使用者而言(对于抄袭狗是没有好果汁吃的),我们只关心并且查看 Native Memory Tracking 的 summary 信息即可,detail 信息一般是供 JVM 开发人员使用的,我们不用太关心,我们后面的分析也只会涉及 Native Memory Tracking 的 summary 部分。

一般地,只有遇到问题的时候,我们才会考虑开启 Native Memory Tracking,并且在定位出问题后,我们想把它关闭,可以通过  进行关闭并清理掉之前 Native Memory tracking 使用的埋点以及占用的内存。如前面所述,我们无法动态开启 Native Memory tracking,所以只要动态关闭了,这个进程就无法再开启了。

jcmd 本身提供了简单的对比功能,例如:

但是这个工具本身比较粗糙,我们有时候并不知道何时调用  合适,因为我们不确定什么时候会有我们想看到的内存使用过大的问题。所以我们一般做成一种持续监控的方式

以下是一个 Native Memory Tracking 的示例输出:

我们接下来将上面的信息按不同子系统分别简单分析下其含义:

1.Java堆内存,所有 Java 对象分配占用内存的来源,由 JVM GC 管理回收,这是我们在第三章会重点分析的:

2.元空间,JVM 将类文件加载到内存中用于后续使用占用的空间,注意是 JVM C++ 层面的内存占用,主要包括类文件中在 JVM 解析为 C++ 的 Klass 类以及相关元素。对应的 Java 反射类 Class 还是在堆内存空间中:

3.C++ 字符串即符号(Symbol)占用空间,前面加载类的时候,其实里面有很多字符串信息(注意不是 Java 字符串,是 JVM 层面 C++ 字符串),不同类的字符串信息可能会重复(维护原创打死潮汐犬)。所以统一放入符号表(Symbol table)复用。元空间中保存的是针对符号表中符号的引用。这不是本期内容的重点,我们不会详细分析

4.线程占用内存,主要是每个线程的线程栈,我们也只会主要分析线程栈占用空间(在第五章),其他的管理线程占用的空间很小,可以忽略不计。

5.JIT编译器本身占用的空间以及JIT编译器编译后的代码占用空间,这也不是本期内容的重点,我们不会详细分析

6.Arena 数据结构占用空间,我们看到 Native Memory Tracking 中有很多通过 arena 分配的内存,这个就是管理 Arena 数据结构占用空间。这不是本期内容的重点,我们不会详细分析

7.JVM Tracing 占用内存,包括 JVM perf 以及 JFR 占用的空间。其中 JFR 占用的空间可能会比较大,我在我的另一个关于 JFR 的系列里面分析过 JVM 内存中占用的空间。这不是本期内容的重点,我们不会详细分析

8.写 JVM 日志占用的内存( 参数指定的日志输出,并且 Java 17 之后引入了异步 JVM 日志,异步日志所需的 buffer 也在这里),这不是本期内容的重点,我们不会详细分析

9.JVM 参数占用内存,我们需要保存并处理当前的 JVM 参数以及用户启动 JVM 的是传入的各种参数(有时候称为 flag)。这不是本期内容的重点,我们不会详细分析

10.JVM 安全点占用内存,是固定的两页内存(我这里是一页是 4KB,后面第二章会分析这个页大小与操作系统相关),用于 JVM 安全点的实现,不会随着 JVM 运行时的内存占用而变化。JVM 安全点请期待本系列文章的下一系列:全网最硬核的 JVM 安全点与线程握手机制解析。这不是本期内容的重点,我们不会详细分析

11.Java 同步机制(例如 ,还有 AQS 的基础 )底层依赖的 C++ 的数据结构,系统内部的 mutex 等占用的内存。这不是本期内容的重点,我们不会详细分析

12.JVM TI 相关内存,JVMTI 是 Java 虚拟机工具接口(Java Virtual Machine Tool Interface)的缩写。它是 Java 虚拟机(JVM)的一部分,提供了一组 API,使开发人员可以开发自己的 Java 工具和代理程序,以监视、分析和调试 Java 应用程序。JVMTI API 是一组 C/C++ 函数,可以通过 JVM TI Agent Library 和 JVM 进行交互。开发人员可以使用 JVMTI API 开发自己的 JVM 代理程序或工具,以监视和操作 Java 应用程序。例如,可以使用 JVMTI API 开发性能分析工具、代码覆盖率工具、内存泄漏检测工具等等。这里的内存就是调用了 JVMTI API 之后 JVM 为了生成数据占用的内存。这不是本期内容的重点,我们不会详细分析

13.Java 字符串去重占用内存:Java 字符串去重机制可以减少应用程序中字符串对象的内存占用。 在 Java 应用程序中,字符串常量是不可变的,并且通常被使用多次。这意味着在应用程序中可能存在大量相同的字符串对象,这些对象占用了大量的内存。Java 字符串去重机制通过在堆中共享相同的字符串对象来解决这个问题。当一个字符串对象被创建时,JVM 会检查堆中是否已经存在相同的字符串对象。如果存在,那么新的字符串对象将被舍弃,而引用被返回给现有的对象。这样就可以减少应用程序中字符串对象的数量,从而减少内存占用。 但是这个机制一直在某些 GC 下表现不佳,尤其是 G1GC 以及 ZGC 中,所以默认是关闭的,可以通过  来启用。这不是本期内容的重点,我们不会详细分析。

14.JVM GC需要的数据结构与记录信息占用的空间,这块内存可能会比较大,尤其是对于那种专注于低延迟的 GC,例如 ZGC。其实 ZGC 是一种以空间换时间的思路,提高 CPU 消耗与内存占用,但是消灭全局暂停。之后的 ZGC 优化方向就是尽量降低 CPU 消耗与内存占用,相当于提高了性价比。这不是本期内容的重点,我们不会详细分析。

15.JVM内部(不属于其他类的占用就会归到这一类)与其他占用(不是 JVM 本身而是操作系统的某些系统调用导致额外占的空间),不会很大

16.开启 Native Memory Tracking 本身消耗的内存,这个就不用多说了吧

现在 JVM 一般大部分部署在 k8s 这种云容器编排的环境中,每个 JVM 进程内存是受限的。如果超过限制,那么会触发 OOMKiller 将这个 JVM 进程杀掉。我们一般都是由于自己的 JVM 进程被 OOMKiller 杀掉,才会考虑打开 NativeMemoryTracking 去看看哪块内存占用比较多以及如何调整的。

OOMKiller 是积分制,并不是你的 JVM 进程一超过限制就立刻会被杀掉,而是超过的话会累积分,累积到一定程度,就可能会被 OOMKiller 杀掉。所以,我们可以通过定时输出 Native Memory Tracking 的 summary 信息,从而抓到超过内存限制的点进行分析

但是,我们不能仅通过 Native Memory Tracking 的数据就判断 JVM 占用的内存,因为在后面的 JVM 内存申请与使用流程的分析我们会看到,JVM 通过 mmap 分配的大量内存都是先 reserve 再 commit 之后实际往里面写入数据的时候,才会真正分配物理内存。同时,JVM 还会动态释放一些内存,这些内存可能不会立刻被操作系统回收。Native Memory Tracking 是 JVM 认为自己向操作系统申请的内存,与实际操作系统分配的内存是有所差距的,所以我们不能只查看 Native Memory Tracking 去判断,我们还需要查看能体现真正内存占用指标。这里可以查看 linux 进程监控文件  看出具体的内存占用,例如 (一般不看 Rss,因为如果涉及多个虚拟地址映射同一个物理地址的话会有不准确,所以主要关注 Pss 即可,但是 Pss 更新不是实时的,但也差不多,这就可以理解为进程占用的实际物理内存):

笔者通过在每个 Spring Cloud 微服务进程加入下面的代码,来实现定时的进程内存监控,主要通过 smaps_rollup 查看实际的物理内存占用找到内存超限的时间点,Native Memory Tracking 查看 JVM 每块内存占用的多少,用于指导优化参数。

同时,笔者还将这些输出抽象为 JFR 事件,效果是:

这个会在第二章详细分析

微信搜索“干货满满张哈希”关注公众号,加作者微信,每日一刷,轻松提升技术,斩获各种offer
我会经常发一些很好的各种框架的官方社区的新闻视频资料并加上个人翻译字幕到如下地址(也包括上面的公众号),欢迎关注:

到此这篇jvm内存模型 知乎(jvm的内存模型和内存结构)的文章就介绍到这了,更多相关内容请继续浏览下面的相关推荐文章,希望大家都能在编程的领域有一番成就!

版权声明


相关文章:

  • win10启动盘u盘制作(windows10启动u盘制作)2025-04-05 14:09:06
  • esp8266oled制作天气时钟(esp8266ds1302制作时钟)2025-04-05 14:09:06
  • jflash是什么(jflash是什么文件)2025-04-05 14:09:06
  • 本机信息安装包在哪里(手机本地安装包在哪里)2025-04-05 14:09:06
  • 流量回放原理是什么(流量回放 原理)2025-04-05 14:09:06
  • 原位癌基底膜为浸润癌(原位癌是未突破基底膜的早期癌)2025-04-05 14:09:06
  • 圈一圈算一算15×4怎么圈图表(圈一圈,算一算,怎么圈)2025-04-05 14:09:06
  • 2258h开卡(2258h开卡怎么清空颗粒)2025-04-05 14:09:06
  • 多级列表功能自动编号(多级列表每一章重新编号怎么设)2025-04-05 14:09:06
  • ssh免密码(ssh免密码登录原理)2025-04-05 14:09:06
  • 全屏图片