• Java堆外内存之七:JVM NativeMemoryTracking 分析堆外内存泄露


    Native Memory Tracking (NMT) 是Hotspot VM用来分析VM内部内存使用情况的一个功能。我们可以利用jcmd(jdk自带)这个工具来访问NMT的数据。

    NMT介绍

    工欲善其事必先利其器,我们先把相关需要的配置和工具介绍清楚,再通过例子来看看具体如何使用NMT。

    打开NMT

    NMT必须先通过VM启动参数中打开,不过要注意的是,打开NMT会带来5%-10%的性能损耗。

    -XX:NativeMemoryTracking=[off | summary | detail]
    # off: 默认关闭
    # summary: 只统计各个分类的内存使用情况.
    # detail: Collect memory usage by individual call sites.
    

    jcmd查看NMT报告

    通过jcmd查看NMT报告以及查看对比情况。

    jcmd <pid> VM.native_memory [summary | detail | baseline | summary.diff | detail.diff | shutdown] [scale= KB | MB | GB]
    
    # summary: 分类内存使用情况.
    # detail: 详细内存使用情况,除了summary信息之外还包含了虚拟内存使用情况。
    # baseline: 创建内存使用快照,方便和后面做对比
    # summary.diff: 和上一次baseline的summary对比
    # detail.diff: 和上一次baseline的detail对比
    # shutdown: 关闭NMT
    

    VM退出时打印NMT

    可以通过下面VM参数在JVM退出时打印NMT报告。

    -XX:+UnlockDiagnosticVMOptions -XX:+PrintNMTStatistics
    

    NMT实战

    症状

    某个服务(C)在客户环境使用后发现其内存占用不断变大且远超Xmx指定的大小,导致整个系统因缺少内存造成其他服务无法启动。当时查看到其RSS大约为11G,-Xmx=6G而且heap利用率不到50%。

    user@hostxxx> prstat -p 2780
    PID USERNAME  SIZE   RSS   STATE  PRI   NICE  TIME     CPU   PROCESS/NLWP
    2780 user    11G     11G   sleep   59    0    44:16:39 0.0%  java/196
    
    user@hostxxx> /opt/jdk1.8.0_40/bin/jstat -gcutil 2780
    S0     S1     E      O      M     CCS    YGC     YGCT       FGC    FGCT     GCT
    0.00 100.00  90.60  46.80  98.02  97.10  11323   4049.745   11     225.345   4275.090
    

    分析

    服务通过-Xmx=6G指定最大堆分配为6G,但实际RSS已达到11G,开始怀疑堆外内存是否有内存泄露。为了有更好详细的数据,就在本地重现这个问题,并且打开了NMT持续监控。

    NMT的Report如下,重点关注每个分类下的commit大小,这个是实际使用的内存大小。

    6739: #进程ID
    
    Native Memory Tracking:
    
    Total: reserved=8491110KB, committed=7220750KB
    -                 Java Heap (reserved=6293504KB, committed=6291456KB) 
                                (mmap: reserved=6293504KB, committed=6291456KB) 
     
    -                     Class (reserved=1107429KB, committed=66189KB) 
                                (classes #11979)
                                (malloc=1509KB #18708) 
                                (mmap: reserved=1105920KB, committed=64680KB) 
     
    -                    Thread (reserved=159383KB, committed=159383KB) 
                                (thread #156)
                                (stack: reserved=158720KB, committed=158720KB)
                                (malloc=482KB #788) 
                                (arena=182KB #310)
     
    -                      Code (reserved=255862KB, committed=41078KB) 
                                (malloc=6262KB #9319) 
                                (mmap: reserved=249600KB, committed=34816KB) 
     
    -                        GC (reserved=449225KB, committed=449225KB) 
                                (malloc=166601KB #1714646) 
                                (mmap: reserved=282624KB, committed=282624KB) 
     
    -                  Compiler (reserved=395KB, committed=395KB) 
                                (malloc=265KB #856) 
                                (arena=131KB #3)
     
    -                  Internal (reserved=146041KB, committed=146041KB) 
                                (malloc=132185KB #276370) 
                                (mmap: reserved=13856KB, committed=13856KB) 
     
    -                    Symbol (reserved=31487KB, committed=31487KB) 
                                (malloc=29209KB #91080) 
                                (arena=2278KB #1)
     
    -    Native Memory Tracking (reserved=33212KB, committed=33212KB) 
                                (malloc=168KB #2575) 
                                (tracking overhead=33044KB)
     
    -               Arena Chunk (reserved=2284KB, committed=2284KB)
                                (malloc=2284KB) 
     
    -                   Unknown (reserved=12288KB, committed=0KB)
                                (mmap: reserved=12288KB, committed=0KB) 
     
    Virtual memory map:
    ......
    

    并且在服务器上通过cron job来定期抓取NMT的report保存下来做分析,而且同时也把其对应的RSS和PMAP都抓取了一份。

    COLLECTOR_PID=`ps -ef|grep "ProcessName" | grep -v grep | awk '{print $2}'`
    OUTDIR=/opt/chkmem
    HOSTNAME=`hostname`
    
    prstat -s rss 1 1 > ${OUTDIR}/${HOSTNAME}_coll_${COLLECTOR_PID}_prstat_`date '+%Y%m%d_%H%M%S'`.txt
    
    /opt/jdk1.8.0_40/bin/jcmd ${COLLECTOR_PID} VM.native_memory detail > ${OUTDIR}/${HOSTNAME}_coll_${COLLECTOR_PID}_nmd_`date '+%Y%m%d_%H%M%S'`.txt
    
    pmap -x ${COLLECTOR_PID} > ${OUTDIR}/${HOSTNAME}_coll_${COLLECTOR_PID}_pmap_`date '+%Y%m%d_%H%M%S'`.txt
    

    分析发现NMT中的Symbol域持续增大,从最开始的几十兆已经增加到了2G左右,而且整个jvm的内存使用量也在持续增加。见下图: 

    验证后发现问题和JDK8的一个bug https://bugs.java.com/view_bug.do?bug_id=8180048 非常类似,测试后也证实确实如此,最后通过升级JDK解决了这个问题。具体是那个组件命中了JDK的这个bug,会在下一篇文章中详细描述。

  • 相关阅读:
    Spring.NET学习笔记(4)对象作用域和类型转换
    spring 依赖注入有什么好处
    加入收藏和设为主页js代码
    hdoj_1027_code
    【C#.NET】ASP.NET状态管理之一:Cookie
    【C#.NET】自定义“验证码”控件(转)
    【C#.NET】C#六种集合性能比较
    【C#.NET】特性和属性
    【C#.NET】ASP.NET 2.0 数据绑定概述(转)
    【C#.NET】ASP.NET状态管理之三:Application
  • 原文地址:https://www.cnblogs.com/duanxz/p/3738858.html
Copyright © 2020-2023  润新知