孤独酒馆|6000+字,30+张图。JAVA线上故障排查全套路总结( 四 )


NMT是Java7U40引入的HotSpot新特性 , 配合jcmd命令我们就可以看到具体内存组成了 。 需要在启动参数中加入 -XX:NativeMemoryTracking=summary 或者 -XX:NativeMemoryTracking=detail , 会有略微性能损耗 。
一般对于堆外内存缓慢增长直到爆炸的情况来说 , 可以先设一个基线jcmd pid VM.native_memory baseline 。
孤独酒馆|6000+字,30+张图。JAVA线上故障排查全套路总结然后等放一段时间后再去看看内存增长的情况 , 通过jcmd pid VM.native_memory detail.diff(summary.diff)做一下summary或者detail级别的diff 。
孤独酒馆|6000+字,30+张图。JAVA线上故障排查全套路总结
孤独酒馆|6000+字,30+张图。JAVA线上故障排查全套路总结可以看到jcmd分析出来的内存十分详细 , 包括堆内、线程以及gc(所以上述其他内存异常其实都可以用nmt来分析) , 这边堆外内存我们重点关注Internal的内存增长 , 如果增长十分明显的话那就是有问题了 。 detail级别的话还会有具体内存段的增长情况 , 如下图 。
孤独酒馆|6000+字,30+张图。JAVA线上故障排查全套路总结此外在系统层面 , 我们还可以使用strace命令来监控内存分配 strace -f -e "brk,mmap,munmap" -p pid这边内存分配信息主要包括了pid和内存地址 。
孤独酒馆|6000+字,30+张图。JAVA线上故障排查全套路总结不过其实上面那些操作也很难定位到具体的问题点 , 关键还是要看错误日志栈 , 找到可疑的对象 , 搞清楚它的回收机制 , 然后去分析对应的对象 。 比如DirectByteBuffer分配内存的话 , 是需要full GC或者手动system.gc来进行回收的(所以最好不要使用-XX:+DisableExplicitGC) 。 那么其实我们可以跟踪一下DirectByteBuffer对象的内存情况 , 通过jmap -histo:live pid手动触发fullGC来看看堆外内存有没有被回收 。 如果被回收了 , 那么大概率是堆外内存本身分配的太小了 , 通过-XX:MaxDirectMemorySize进行调整 。 如果没有什么变化 , 那就要使用jmap去分析那些不能被gc的对象 , 以及和DirectByteBuffer之间的引用关系了 。
GC问题堆内内存泄漏总是和GC异常相伴 。 不过GC问题不只是和内存问题相关 , 还有可能引起CPU负载、网络问题等系列并发症 , 只是相对来说和内存联系紧密些 , 所以我们在此单独总结一下GC相关问题 。
我们在cpu章介绍了使用jstat来获取当前GC分代变化信息 。 而更多时候 , 我们是通过GC日志来排查问题的 , 在启动参数中加上-verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps来开启GC日志 。 常见的Young GC、Full GC日志含义在此就不做赘述了 。
针对gc日志 , 我们就能大致推断出youngGC与fullGC是否过于频繁或者耗时过长 , 从而对症下药 。 我们下面将对G1垃圾收集器来做分析 , 这边也建议大家使用G1-XX:+UseG1GC 。
youngGC过频繁youngGC频繁一般是短周期小对象较多 , 先考虑是不是Eden区/新生代设置的太小了 , 看能否通过调整-Xmn、-XX:SurvivorRatio等参数设置来解决问题 。 如果参数正常 , 但是young gc频率还是太高 , 就需要使用Jmap和MAT对dump文件进行进一步排查了 。
youngGC耗时过长耗时过长问题就要看GC日志里耗时耗在哪一块了 。 以G1日志为例 , 可以关注Root Scanning、Object Copy、Ref Proc等阶段 。 Ref Proc耗时长 , 就要注意引用相关的对象 。 Root Scanning耗时长 , 就要注意线程数、跨代引用 。 Object Copy则需要关注对象生存周期 。 而且耗时分析它需要横向比较 , 就是和其他项目或者正常时间段的耗时比较 。 比如说图中的Root Scanning和正常时间段比增长较多 , 那就是起的线程太多了 。


推荐阅读