【JVM内存】系统性排查JVM内存问题的思路

embedded/2024/10/21 11:54:47/

【JVM内存】系统性排查JVM内存问题的思路

背景

前言

  • 遇到过几次JVM堆外内存泄露的问题,每次问题的排查、修复都耗费了不少时间,问题持续几月、甚至一两年
  • 我们将这些排查的思路梳理成一套系统的方法,希望能给对JVM内存分布内存泄露问题有更清晰的理解。

这篇文章能带给你什么

  • 了解JVM的内存分布.
  • 更合理地去设置JVM参数。
  • 能大大提升排查JVM内存问题的效率。

本文的限定范围

  • JDK版本
  • JDK8*,其他JDK版本可能有所差异。**
  • 重点讲解堆外内存**
  • 堆内的内存问题文章比较多,一般是dump堆内存,然后分析即可。

文章讲解的顺序

  • 讲解JVM内存分布,了解有哪些内存区域、JVM参数等。堆内相关的文章比较多,堆外的比较少,所以重点讲解堆外的。
  • 讲解排查JVM内存问题的思路。

JVM内存分布

JVM内存分布

  • 【重点中的重点】JVM内存分布图
  • 总体分为堆内内存、堆外内存。
  • 在这里插入图片描述

【重点】Heap Space(堆内内存)

  • 重点关注新生代老年代

Young Generation新生代

  • 用于存放新创建的对象,分为一个Eden区两个Survivor区
  • Young GC发生时会回收该块内存。

老年代(Old Generation)

作用
  • 主要用于存放生命周期较长的对象。
何时回收
  • Old GC发生时会回收该块内存,一般触发Old GC时会伴随着一次Young GC
参数
  • -Xmn: 新生代的内存大小
  • -Xms: Heap的初始大小
  • -Xmx: Heap的最大大小
问答

【重点】Non-Heap Space(非堆内存、堆外内存)

什么是堆外内存

  • Non-Heap Space 翻译为非堆内存,也被称为Off-Heap(堆外内存),大家习惯于叫这部分内存为堆外内存。查看了很多国内外文章,对于这块内存,没有很统一的定义

  • 较可信的是分为下面两种定义:

  • 广义上的Non-Heap

  • 除开Heap以外的所有内存,包括MetaSpace、NativeMemory(JNI Memory、Direct Memory等)、Stack、Code Cache等。

  • 下面讲解的Non-Heap是针对于广义的定义

  • 狭义上的Non-Heap

  • 只包含Metaspace、code_cache。

  • 注意:

  • 监控系统里会有Non-Heap的监控,例如SkyWalking、Arthas的Non-Heap指标,都是通过JDK自带的MemoryMXBean方法获取的。

  • 所以一般监控系统采集的Non-Heap只是Heap以外的一部分内存!还需要留意NativeMemory等等内存。

  • 监控数据示例;

  • 在这里插入图片描述

  • 对应的代码:

  • @Override
    public long getNonHeapMemoryMax() {return memoryMXBean.getNonHeapMemoryUsage().getMax();
    }@Overridepublic long getNonHeapMemoryUsed() {return memoryMXBean.getNonHeapMemoryUsage().getUsed();
    }

【重点】MetaSpace(元数据空间)

  • 用于存储类元数据(如类定义和方法定义)的内存区域。Metaspace 在 JDK 8 中取代了永久代(PermGen)。
相关参数
  • -XX:MetaspaceSize=
  • -XX:MaxMetaspaceSize=
  • -XX:MetaspaceSize 参数设置了元空间的初始大小,在 JDK 8 中,-XX:MetaspaceSize 参数的默认值为 21 MB。。当元空间使用量达到这个值时,JVM 将触发 Full GC(也会附带younggc) 来尝试回收不再需要的类元数据以及相关资源。
  • 如果回收后元空间仍然无法满足需求,那么 JVM 将尝试扩展元空间的大小。
  • 问答:很多同学奇怪,我们有时看到某些应用启动一段时候,堆内存使用量不高,为何会发生一次FULL GC?
  • 这很可能是因为应用的JVM参数里没有设置-XX:MetaspaceSize,或者-XX:MetaspaceSize设置的比较小。
  • -XX:MaxMetaspaceSize 参数设置了元空间的最大大小。元空间会根据需要动态扩展,但不会超过这个设置的最大值。当元空间使用量超过这个值时,JVM 将触发 Full GC(也会附带younggc),尝试回收不再需要的类元数据以及相关资源。如果回收后元空间仍然无法满足需求,那么 JVM 将抛出java.lang.OutOfMemoryError: Metaspace错误。因此,这个参数既与 Full GC 相关,也与 OOM 相关。
问答
  • 如何合理设置-XX:MaxMetaspaceSize参数?
  • 建议JVM启动参数指定-XX:MaxMetaspaceSize,一般大小256M足够,因为默认值无限大,如果出现频繁加载class等情况,容易出现OOM。

OOM异常
  • OOM报错: java.lang.OutOfMemoryError: Metaspace

Native Memory(本地内存)

Direct Memory(直接内存)
JNI Memory(JNI内存)
  • JNI (Java Native Interface) memory是指Java应用程序与本地代码交互时使用的内存。Java Native Interface (JNI) 是 Java 与本地(如 C 或 C++)代码进行交互的桥梁。

  • JNI方法
  • 使用方式:在Java中使用native关键字定义方法,并在C/C++代码中实现相关的本地方法。

  • 示例:

  • private native int inflateBytes(long addr, byte[] b, int off, int len);
    
  • 该native方法内部也会申请内存用以存储数据,这部分内存属于JNI内存的一部分。

  • 参数
  • 无特定的 JVM 参数,但需要在本地代码中管理内存分配和释放。

  • 注意:与-XX:MaxDirectMemorySize=无关。

  • JNI内存分配过程
  • 在这里插入图片描述

Stack(栈内存)

Stack介绍
  • 用于存储线程执行过程中的局部变量、方法调用、操作数栈等。
  • 栈内存由JVM自动管理,每个线程都有一个独立的栈
  • 栈内存与堆内存相互独立,它们之间不共享数据。
  • 分为VM Stack(Java虚拟机栈)、Native Stack(本地方法栈)
分类

特殊内存

MMap
  • 介绍
  • 底层用的操作系统的mmap,将文件或文件的一部分映射到内存中的技术,通过内存映射文件可以实现高效的文件读写操作。

  • 使用方式
  • FileChannel fileChannel = FileChannel.open(Paths.get("a.txt"), StandardOpenOption.READ, StandardOpenOption.WRITE); // 以读写的方式打开文件通道MappedByteBuffer buf = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, fileChannel.size()); // 将整个文件映射到内存
    
  • 参数
  • 无特定的 JVM 参数。

  • 注意:与-XX:MaxDirectMemorySize=无关。

  • 框架和中间件
  • Lucene、RocketMQ、Kafka等

  • 注意
  • javammapJVM_272">java中mmap的内存不属于JVM进程占用的内存!
  • 当使用java.nio.channels.FileChannel#map方法时,分配的内存实际上是由操作系统管理的,并不是由JVM管理。这部分内存是映射到文件的内存区域,又称为内存映射文件(Memory-Mapped File)。在操作系统中,这部分内存被分类为文件缓存,而非Java进程的私有内存。

  • 内存映射文件允许将文件或文件的一部分映射到进程的地址空间。一旦建立了映射,进程可以像访问常规内存一样访问文件。操作系统会负责将对映射内存的更改写回磁盘。

  • 因此,当你使用一些命令(如ps、top)查看Java进程的内存使用时,这部分内存映射文件的使用量并不会直接计算到进程的私有内存中。这部分内存使用在某种程度上是透明的,但仍然受操作系统的文件缓存管理。

  • 在Linux系统中,可以通过查看**/proc/meminfo**文件来获取关于内存映射文件的信息。

  • 该结论基于实验:使用mmap方式写入2G文件,用arthas的memory命令查看JVM进程对应mmap使用量,已经是2G,但实际JVM的内存占有量,只有703M,这是因为mmap的内存是由操作系统控制的,不算在进程占用。

  • 内存分配过程
  • 在这里插入图片描述

【重点】内存排查工具

堆内内存相关工具

  • 整理了堆内内存相关的工具。
  • 建议从上往下逐一执行命令,从整体到局部,逐步排查出具体的问题。
  • 在这里插入图片描述
堆外内存相关工具
  • 不同的内存区域可以使用不同的命令进行排查,同时也留意合理设置对应内存区域的参数。
  • 在这里插入图片描述

JVM内存使用量过大问题排查思路

整体的排查思路

  • 使用量大原因一般分为

  • 数据量大,自然使用量大

  • JVM内存泄露,导致可以释放的内存未释放

  • JVM内存泄露:

  • 在JVM运行过程中,由于(1)未正确释放不再使用的内存 (2)或者执行内存释放步骤后内存却未回收,导致内存占用持续增长,甚至最终耗尽导致OOM(内存溢出)的现象

  • 发现问题、提前预知问题

  • 依赖于监控告警:falcon、prometheus、troy等,主要是内存、GC相关

  • 发现问题、提前预知问题

  • 先止损,一般处理方式是通过重启,或者手动触发fullgc。

  • 保留现场

  • 如果条件允许一定不要直接操作重启、回滚等动作恢复,优先通过摘掉流量的方式来恢复,例如:通过dubbo控制台将某个provider实例禁止访问。

  • 然后将堆(手工dump、或者指定-XX:+HeapDumpOnOutOfMemoryError)、栈(jstack命令导出)、GC 日志等关键信息保留下来,不然错过了定位根因的时机,后续想要复现、解决的难度将大大增加。

  • 确定是那个进程的问题

  • 当出现内存问题时,需要确认是那个进场的问题。

  • 当发生进程A被操作系统的OOM-killer杀掉时,可能不是A的问题,可能是进程B占用内存过多,导致系统内存不够用,

  • 然后触发OOM-killer计算出oom分数(根据内存、进程运行时间等打分,参考文档),选择杀掉了进程A。

  • 分析日志

  • 分析应用日志是否有outofmemory等关键字;

  • 分析系统日志/var/log/messages或者dmesg观察outofmemory的情况、进程运行的记录;

  • 分析应用GC日志;

  • 查找不同内存区域占比判断可疑的内存

  • 根据命令、监控平台,逐个分析内存区域大户:Heap、MetaSpace、DirectMemory、JNI Memory。

  • 分析可疑内存数据内容

  • 分析内存占用大的区域中的数据,也可以辅助定位对应源码。

  • 分析可疑内存调用栈

  • 对于java而言,推荐使用arthas的trace和stack命令,但是arthas无法对native方法进行拦截,此时可以借助jstack或者arthas拦截可能调用native方法的上层方法。

  • 对于JNI Memory,这块内存是C、C++等native方法相关的,需要用gperftools、gdb等工具进行分析。

  • 复现问题

  • 在没有了解问题原因、内存增长规律的情况下,想要复现问题,有时是很困难的!可能要花费很长时间、且需要些运气。

  • 所以我们尽量保留问题现场,方便找出规律。

  • 内存泄漏按发生方式来分类:

  • 在这里插入图片描述

  • 修复问题

  • JVM内存问题一般是代码问题、JVM参数问题、malloc内存分配库等,针对不同类型的问题进行修复。

案例

  • 案例遇到比较多:
  • 不合理地使用fastjson,导致频繁地在创建、加载class (2)未设置-XX:MaxMetaspaceSize 导致了内存一直增长,直到OOM
  • JNI Memory内存泄漏
  • JVM参数-XX:SoftRefLRUPolicyMSPerMB和metaspace导致的fullgc
  • vim命令编辑文件导致的业务应用的进程被oom-killer杀掉

总结

  • 首先是看这张图,了解JVM内存的分布。

  • 在这里插入图片描述

  • 遇到内存问题,先根据通用的排查思路一遍内存的使用情况。

  • 有很多JDK、Linux内存相关的命令,大家可以去尝试一下,先查大范围的内存占用,再逐步定位到具体的内存区域、代码、参数等。

  • 重启程序、系统能临时解决很多内存问题,但是,建议去深究一下,会学到很多JVM内存管理和Linux内存管理的知识,还是很有趣的。

  • 此外,掌握了JVM内存管理的设计后,发现很多程序的内存是比较浪费的,可以对JVM参数做针对性优化,能减少很多机器资源。


http://www.ppmy.cn/embedded/90187.html

相关文章

CTFSHOW 萌新 web9 解题思路和方法(利用system函数执行系统命令)

点击题目链接,从题目页面显示的代码中可以看到我们可以使用命令执行漏洞查看网站的文件: 我们首先使用system函数并使用 ls 命令查看当前目录下的所有文件: 因为题目中提示flag在config.php文件中,所有可以直接读取该文件 当然&am…

alibaba cloud linux+JDK+TOMCAT+NGINX+PHP+MYSQL配置实践

CentOs要停止维护了,一直在服务器上用的CentOs7也最迟到2024年6月了,这次给公司新购一台备用服务器,在选择操作系统的时候,考虑了一下,决定试用一下阿里云的alibaba cloud linux。 alibaba cloud linux分为2和3版本&am…

window bat批处理脚本

参考: https://www.cnblogs.com/dirgo/p/18108455 https://blog.csdn.net/AnChenliang_1002/article/details/131288871 https://www.cnblogs.com/jingxian666/p/16814375.html 什么是BAT 全称即Batch,批处理,是一类可执行的文本文件&#…

Stable Diffusion绘画 | 文生图设置详解—随机种子数(Seed)

随机种子数(Seed) Midjourney 也有同样的概念,通过 --seed 种子数值 来使用。 每次操作「生成」所得到的图片,都会随机分配一个 seed值,数值不同,生成的画面就会不同。 默认值为 -1:每次随机分…

数据结构与算法--队列

文章目录 提要队列的定义队列的认识队列的应用队列的抽象数据类型队列的存储结构队列的链式存储结构与实现链队的进队和出队操作链队的数据类型初始化链队列入队操作出队操作队列的顺序存储结构与实现顺序队列的假溢出问题队列上溢循环队列循环队列取下一相邻单元下标运算队满与…

24年电赛——自动行驶小车(H题)基于 CCS Theia -陀螺仪 JY60 代码移植到 MSPM0G3507(附代码)

前言 只要搞懂 M0 的代码结构和 CCS 的图形化配置方法,代码移植就会变的很简单。因为本次电赛的需要,正好陀螺仪部分代码的移植是我完成的。(末尾附全部代码) 一、JY60 陀螺仪 JY60特点 1.模块集成高精度的陀螺仪、加速度计&…

PHP中的魔术常量(如__FILE__,__LINE__)及其用途

在PHP中,魔术常量是一组预定义的常量,它们会根据它们使用的上下文环境而改变其值。这些常量以两个下划线字符开始和结束。魔术常量提供了有关代码执行环境的有用信息,例如当前文件的路径、当前行号等。 以下是几个常用的PHP魔术常量及其用途…

安装MongoDB UI客户端工具:mongodb-compass-1.40.2-win32-x64.msi

文章目录 1、安装 mongodb-compass-1.40.2-win32-x64.msi2、安装后配置链接地址: 1、安装 mongodb-compass-1.40.2-win32-x64.msi 2、安装后配置链接地址: