JVM 堆外内存泄漏分析(二)

标签: JVM 堆外内存泄漏 | 发表时间:2019-09-16 08:01 | 作者:coderbee
出处:https://coderbee.net

关于 堆外内存的组成可以看上一篇文章 JVM 堆外内存泄漏分析(一)

1. NMT

NMT(Native Memory Tracking)是 HotSpot JVM 引入的跟踪 JVM 内部使用的本地内存的一个特性,可以通过 jcmd 工具访问 NMT 数据。NMT 目前不支持跟踪第三方本地代码的内存分配和 JDK 类库。

NMT 不跟踪非 JVM 代码的内存分配,本地代码里的内存泄露需要使用操作系统支持的工具来定位。

1.1 开启 NMT

启用 NMT 会带来 5-10% 的性能损失。NMT 的内存使用率情况需要添加两个机器字 word 到 malloc 内存的 malloc 头里。NMT 内存使用率也被 NMT 跟踪。

启动命令: -XX:NativeMemoryTracking=[off | summary | detail]

off:NMT 默认是关闭的;
summary:只收集子系统的内存使用的总计数据;
detail:收集每个调用点的内存使用数据。

1.2 jcmd 访问 NMT 数据

命令: jcmd <pid> VM.native_memory [summary | detail | baseline | summary.diff | detail.diff | shutdown] [scale= KB | MB | GB]

option desc
summary 按分类打印汇总数据
detail 按分类打印汇总数据
打印虚拟内存映射
按调用点打印内存使用汇总
baseling 创建内存使用快照用于后续对比
summary.diff 基于最新的基线打印一份汇总报告
detail.diff 基于最新的基线打印一份明细报告
shutdown 关闭 NMT

在 NMT 启用的情况下,可以通过下面的命令行选项在 JVM 退出时输出最后的内存使用数据:
-XX:+UnlockDiagnosticVMOptions -XX:+PrintNMTStatistics

1.3 使用 NMT 检测内存泄露

  1. 开启 NMT,用命令: -XX:NativeMemoryTracking=summary|detail
  2. 创建基线,用命令: jcmd <pid> VM.native_memory baseline
  3. 观察内存变化: jcmd <pid> VM.native_memory detail.diff

NMT 数据输出解释:

reserved memory:预订内存,不表示实际使用,最主要的是申请了一批连续的地址空间;(OS 角度)
commited memory:实际使用的。(OS 角度)
对于 64 位的系统,地址空间几乎是无限的,但越来越多的内存 committed,可能会导致 swapping 或本地 OOM 。

以下示例来自 https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/tooldescr007.html

-XX:NativeMemoryTracking=summaryjcmd <pid> VM.native_memory summary 输出:

  Total:  reserved=664192KB,  committed=253120KB  <--- total memory tracked by Native Memory Tracking

-     Java Heap (reserved=516096KB, committed=204800KB)  <--- Java Heap
                (mmap: reserved=516096KB, committed=204800KB)

-     Class (reserved=6568KB, committed=4140KB)     <--- class metadata
            (classes #665)                          <--- number of loaded classes
            (malloc=424KB, #1000)                   <--- malloc'd memory, #number of malloc
            (mmap: reserved=6144KB, committed=3716KB)

-     Thread (reserved=6868KB, committed=6868KB)
            (thread #15)                            <--- number of threads
            (stack: reserved=6780KB, committed=6780KB) <--- memory used by thread stacks
            (malloc=27KB, #66)
            (arena=61KB, #30)                       <--- resource and handle areas

-     Code (reserved=102414KB, committed=6314KB)
           (malloc=2574KB, #74316)
           (mmap: reserved=99840KB, committed=3740KB)

-     GC (reserved=26154KB, committed=24938KB)
           (malloc=486KB, #110)
           (mmap: reserved=25668KB, committed=24452KB)

-     Compiler (reserved=106KB, committed=106KB)
               (malloc=7KB, #90)
               (arena=99KB, #3)

-     Internal (reserved=586KB, committed=554KB)
               (malloc=554KB, #1677)
               (mmap: reserved=32KB, committed=0KB)

-     Symbol (reserved=906KB, committed=906KB)
             (malloc=514KB, #2736)
             (arena=392KB, #1)

-     Memory Tracking (reserved=3184KB, committed=3184KB)
                      (malloc=3184KB, #300)

-     Pooled Free Chunks (reserved=1276KB, committed=1276KB)
                         (malloc=1276KB)

-     Unknown (reserved=33KB, committed=33KB)
              (arena=33KB, #1)

-XX:NativeMemoryTracking=detailjcmd <pid> VM.native_memory detail 组合的输出示例:

2. 系统层面的分析思路

内存泄漏一般都不是突然猛增到极限,而是一个慢慢增长的过程,这样我们可以选取两个时间的内存来进行对比,看新增的内存里到底存的是什么内容。

2.0 gdb 方式

gdb 导出指定地址范围的内存块的内容 :

  sudo gdb --batch --pid 2754 -ex "dump memory a.dump 0x7f1023ff6000 0x7f1023ff6000+268435456"

然后用 hexdump -C /tmp/memory.binstrings /tmp/memory.bin |less 查看内存块里的内容。

如果内存块里存的是文本信息,这样是可以看出存的是什么内容的,如果是二进制的内存,就没法看了。

2.1 jstack/jmap + core dump

先生成 core dump,然后从 core dump 里提取线程栈、JVM 堆 dump,JDK 8 下提取成功:

  # 使用 gcore 命令生成 core dump,
gcore 1791

# 使用 jstack 从 core dump 文件提取线程信息
~/zuul-jdk/zulu8.40.0.25-ca-jdk8.0.222-linux_x64/bin/jstack ~/zuul-jdk/zulu8.40.0.25-ca-jdk8.0.222-linux_x64/bin/java core.1791

# 使用 jmap 从 core dump 文件提取 JVM 堆 dump
~/zuul-jdk/zulu8.40.0.25-ca-jdk8.0.222-linux_x64/bin/jmap -dump:format=b,file=zuul.jmap.hprof ~/zuul-jdk/zulu8.40.0.25-ca-jdk8.0.222-linux_x64/bin/java core.1791

# jstack、jmap 从 core dump 里提取信息的方式,exec 一般是指向可执行命令 java 的路径
jstack exec core-file
jmap <options> exec core-file

2.2 jhsdb

jhsdb: hsdb 是 HotSpot debugger 的简称,是 JDK9 开始引入的一个调试工具。

  $ jhsdb
    clhsdb              command line debugger
    hsdb                ui debugger
    debugd --help       to get more information
    jstack --help       to get more information
    jmap   --help       to get more information
    jinfo  --help       to get more information
    jsnap  --help       to get more information

在 openJDK 11 提取实操失败了,生成堆 dump 时会出现一些内存地址读取失败。

用 jstack 从 core dump 提取信息:

  sudo jstack -J-d64 /usr/bin/java core.2316

jhsdb jstack --exe /usr/bin/java --core core.2316

-d64 表示64位的系统,这两个也是网上找的,没有实际成功。

3. 参考资料


欢迎关注我的微信公众号: coderbee笔记,可以更及时回复你的讨论。

相关 [jvm 内存泄漏 分析] 推荐:

JVM 堆外内存泄漏分析(一)

- - coderbee笔记
Java 应用部署在 Kubernetes 集群里,每个容器只运行一个进程, JVM 的启动命令是打包在镜像文件里的. 常规的方式是采用 -Xmx4g -Xms2g 这样的参数来指定 JVM 堆的最大、最小尺寸,如果需要调整堆大小就需要重新打包镜像. 为了避免因为修改堆大小而重新打包,从 JDK 8u191 版本开始支持 JVM 感知容器资源限制,这样在调整 JVM 内存分配时就不需要重新打包镜像文件,采用下面的参数来使 JVM 在启动时感知到容器的资源限制,并设定堆的大小:.

JVM 堆外内存泄漏分析(二)

- - coderbee笔记
关于 堆外内存的组成可以看上一篇文章 JVM 堆外内存泄漏分析(一). NMT(Native Memory Tracking)是 HotSpot JVM 引入的跟踪 JVM 内部使用的本地内存的一个特性,可以通过 jcmd 工具访问 NMT 数据. NMT 目前不支持跟踪第三方本地代码的内存分配和 JDK 类库.

一次完整的JVM堆外内存泄漏故障排查记录

- -
记录一次线上JVM堆外内存泄漏问题的排查过程与思路,其中夹带一些. 「JVM内存分配的原理分析」以及. 「常用的JVM问题排查手段和工具分享」,希望对大家有所帮助. 在整个排查过程中,我也走了不少弯路,但是在文章中我仍然会把完整的思路和想法写出来,当做一次经验教训,给后人参考,文章最后也总结了下内存泄漏问题快速排查的几个原则.

String substring的内存泄漏分析和优化方法

- - ITeye博客
本文将对String.substring方法可能产生内存泄漏的问题进行分析,并给出相应的优化方法. String.substring内存泄漏分析. 首先看一下JDK6 String.substring的源代码:. 从上述的源代码可以看出,使用substring获取子字符串方法中,原有字符串的内容value(char[])将继续重用.

使用Memory Analyzer tool(MAT)分析内存泄漏

- - 移动开发 - ITeye博客
前言的前言:本文是自2005年8月以来,首次在一个月之内发布三篇文章. 谨以此文献给这么多年始终不济的我. 北漂快两年了,何时能回到故乡,回去后又会怎样,也许永远是个未知……. 在平时工作过程中,有时会遇到OutOfMemoryError,我们知道遇到Error一般表明程序存在着严重问题,可能是灾难性的.

内存泄漏

- - CSDN博客系统运维推荐文章
程序申请了堆空间,但是“忘记”释放,导致该块区域在程序结束前无法被再次使用导致的. 泄漏时间长了,就会导致用户空间内存不足,严重的导致死机. 如果泄漏比较严重,很容易察觉;但是有些泄漏很缓慢,不容易察觉,但是软件会运行很长时间后,会慢慢导致严重问题,而且当发现症状的时候,基本上已经是比较晚的时候了,想要识别泄漏,还是可以实现的,本篇文章来聊聊内存操作的原理.

jvm内存映像分析

- - ITeye博客
     jdk自带的jmap就是java内存映像工具,可以用于上生成堆转储快照:. 在eclipse中启动一个java类,打开jdk安装目录下的C:\Program Files\Java\jdk1.6.0_11\bin目录,双击jconsole.exe,显示连接窗口:.  ,单击pid为6920的选项,点连接进入,可以看到jvm运行时的多种参数,.

JVM原理分析笔记

- - Java - 编程语言 - ITeye博客
1.Javac编译器的作用. 将符合Java语言规范的源代码转化成符合Java虚拟机规范的Java字节码. 2.编译器主要的几个处理阶段. 词法分析、语法分析、语义分析和代码生成,基于访问者模式来遍历语法树的过程. 二.ClassLoader. 将Class加载到JVM中,审查每个类应该由谁加载,将Class字节码重新解析成JVM统一要求的对象格式.

MAT JVM内存分析

- - 开源软件 - ITeye博客
我们使用的是 Eclipse Memory Analyzer V0.8,Sun JDK 6. 和其他插件的安装非常类似,MAT 支持两种安装方式,一种是“单机版“的,也就是说用户不必安装 Eclipse IDE 环境,MAT 作为一个独立的 Eclipse RCP 应用运行;另一种是”集成版“的,也就是说 MAT 也可以作为 Eclipse IDE 的一部分,和现有的开发平台集成.

JVM的逃逸分析

- - SegmentFault 最新的文章
JVM通过逃逸分析,那些逃不出方法的对象会在栈上分配. EscapeAnalysis,逃逸分析,指的是虚拟机在 运行期通过计算分析将原本在堆上分配的对象改成在栈中分配,这样的好处是栈上分配的对象随着线程的结束而自动销毁,不依赖于GC,可以降低垃圾收集器运行的频率. JVM判断新创建的对象是否逃逸的依据有两个:.