【JVM内存】系统性排查JVM内存题目的思绪

打印 上一主题 下一主题

主题 551|帖子 551|积分 1653

【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的最大大小
问答



  • 设置了Xms,那是不是JVM一启动就使用了这么多的物理内存来划分给Heap?
  • 分情况而定:
  • (1) 如果未设置了-XX:AlwaysPreTouch,则现实是使用的是假造内存,给了一张空头支票,只在首次访问时,比方存放一批新的Java对象数据,但原来申请的内存不敷用了,必要新的内存来,这时才必要分配物理内存,也就是通过缺页异常进入内核中,再由内核来分配内存,再交给JVM进程使用。
  • 一般情况,不会设置-XX:AlwaysPreTouch。
  • (2) 如果设置了-XX:AlwaysPreTouch,则JVM启动时,则不仅分配Xms的大小的假造内存,还会使用物理内存、添补整个堆。
  • 设置-XX:AlwaysPreTouch可以提前申请好物理内存,淘汰程序运行过程中发生的物理内存分配带来的延迟,可以提升性能。比方摆设elasticsearch节点时,可以指定该参数,提升性能。

  • XMX设置多大符合?
  • 一般的应用,XMX可以设置为物理内存的1/2到2/3,较充分地去使用内存。
  • 必要较多地使用Heap外内存应用,物理内存不要超过1/2,比方ElasticSearch、RocketMQ-broker、Kafka等中间件,必要大量读写文件,操纵系统必要大量的Page Cache,才气有足够的缓存提高性能,所以JVM Heap不要过大,以预留给非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等等内存。
  • 监控数据示例;

  • 对应的代码:
    1. @Override
    2. public long getNonHeapMemoryMax() {
    3.   return memoryMXBean.getNonHeapMemoryUsage().getMax();
    4. }
    5. @Override
    6. public long getNonHeapMemoryUsed() {
    7.   return memoryMXBean.getNonHeapMemoryUsage().getUsed();
    8. }
    复制代码
【重点】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(直接内存)



  • 是Java NIO 框架引入的一种内存分配机制,答应在堆外分配内存以便更高效地执行 I/O 操纵,通常用于NIO网络编程,JVM使用该内存作为缓冲区,提升I/O性能。

  • 创建 Direct Buffer 的方法
  • ByteBuffer.allocateDirect()
  • 该方法分配内存:内部用的是unsafe.allocateMemory(size)方法,但不属于Java NIO库的一部门,且jdk官方不推荐直接使用unsafe.allocateMemory(size)方法,该方法不受-XX:MaxDirectMemorySize参数控制,轻易导致内存被无节制地使用,所以推荐ByteBuffer.allocateDirect()方法分配内存。
  • 相关参数
  • -XX:MaxDirectMemorySize=
  • 如果未设置-XX:MaxDirectMemorySize,默认值等于Xmx。
  • 可指定最大直接内存大小,DirectMemory会超过MaxDirectMemorySize前,触发FULL GC(也会附带Young GC),堆内DirectByteBuffer等会对象采取时,会触发对象的clean逻辑,释放该对象关联的DirectMemory,当gc后还是不敷,就会OOM。
  • 问答:怎样合理设置-XX:MaxDirectMemorySize参数?
  • 因为默认值等于Xmx,所以发起指定一下MaxDirectMemorySize,Netty等框架会用到DirectMemory,且一般设置1G足够
  • 框架和中间件
  • Netty(底层使用Java NIO技能)、Java NIO库(Java NIO库本身使用直接缓冲区进行高性能网络和文件I/O操纵)等。
  • 当申请堆外内存时,NIO 和 Netty 会比力计数器字段和最大值的大小,如果计数器的值超过了最大值的限制,会抛出 OOM 的异常。
  • OOM效果
  • NIO 中是:OutOfMemoryError: Direct buffer memory。
  • Netty 中是:OutOfDirectMemoryError: failed to allocate capacity byte(s) of direct memory (used: usedMemory , max: DIRECT_MEMORY_LIMIT )
JNI Memory(JNI内存)



  • JNI (Java Native Interface) memory是指Java应用程序与本地代码交互时使用的内存。Java Native Interface (JNI) 是 Java 与本地(如 C 或 C++)代码进行交互的桥梁。
  • JNI方法
  • 使用方式:在Java中使用native关键字界说方法,并在C/C++代码中实现相关的本地方法。
  • 示例:
    1. 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(本地方法栈)
分类



  • VM Stack(Java假造机栈)
  • 用于存储线程执行Java方法时所需的信息。
  • 当一个方法执行完成后,其对应的栈帧会从栈中弹出,释放该方法所占用的内存空间
  • 每个线程对应一个Java线程栈,大小由-Xss参数控制,默认是1M,当超过1M会报错StackOverFlowError
  • Native Stack(本地方法栈)
  • 用于存储本地方法(通过Java Native Interface,JNI调用的方法)的信息。
  • 本地方法栈与Java假造机栈的重要区别在于,它是为本地方法提供内存空间,而不是Java方法。
特别内存

MMap



  • 介绍
  • 底层用的操纵系统的mmap,将文件或文件的一部门映射到内存中的技能,通过内存映射文件可以实现高效的文件读写操纵。
  • 使用方式
    1. FileChannel fileChannel = FileChannel.open(Paths.get("a.txt"), StandardOpenOption.READ, StandardOpenOption.WRITE); // 以读写的方式打开文件通道
    2. MappedByteBuffer buf = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, fileChannel.size()); // 将整个文件映射到内存
    复制代码
  • 参数
  • 无特定的 JVM 参数。
  • 注意:与-XX:MaxDirectMemorySize=无关。
  • 框架和中间件
  • Lucene、RocketMQ、Kafka等
  • 注意
  • 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参数做针对性优化,能淘汰许多机器资源。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

用多少眼泪才能让你相信

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表