【问题排查系列】JDK1.8 下内存不断增长排查及解决

概述

通过对搜索集群中的内存不断增长问题的排查,总结排查内存方面的方法和经验。以便记录和参考。
问题表现
  • 发布之后机器内存不断上涨。需要重启才能得到解决。
解决过程
jmap 排查堆内存阶段
  • 使用传统的方式dump内存,然后使用Jprofiler或mat进行分析。
  • 这里使用了对照的方式,即在重启后立即dump堆和运行一天后dump进行快照比对,发现差异较小,且使用了dump:live 和非live两种方式,均未发现堆内有明显异常。
  • 【【问题排查系列】JDK1.8 下内存不断增长排查及解决】详细操作方式这里不在赘述。
    使用NMT排查堆外内存(native memory)
  • 需要添加参数 -XX:NativeMemoryTracking=detail 据说添加了之后性能会下降 5% ~ 10% ,我添加了,没下降。不过还是建议线上慎重,添加一台机器排查问题即可。
  • 设置NMT 的基线: jcmd VM.native_memory baseline 设置基线之后即可标记一个基准内存状态,过一段时间之后可以比较内存的变化,哪部分增长的最多。
  • 过一段时间后,使用 jcmd VM.native_memory detail.diff scale=MB 查看内存的变化。
  • 经过一段时间之后变化如下:
Native Memory Tracking:Total: reserved=15048MB +73MB, committed=13993MB +74MB-Java Heap (reserved=10240MB, committed=10240MB) (mmap: reserved=10240MB, committed=10240MB)-Class (reserved=1224MB, committed=223MB) (classes #30779 +1) (malloc=6MB #100242 +153) (mmap: reserved=1218MB, committed=218MB)-Thread (reserved=1457MB +5MB, committed=1457MB +5MB) (thread #1444 +4) (stack: reserved=1449MB +5MB, committed=1449MB +5MB) (malloc=5MB #7227 +20) (arena=3MB #2887 +8)-Code (reserved=286MB, committed=251MB) (malloc=42MB #41476 +94) (mmap: reserved=244MB, committed=209MB)-GC (reserved=520MB +16MB, committed=520MB +16MB) (malloc=108MB +16MB #153709 +200) (mmap: reserved=412MB, committed=412MB)-Compiler (reserved=5MB, committed=5MB) (malloc=5MB #5673 +3)-Internal (reserved=610MB +3MB, committed=610MB +3MB) (malloc=609MB +3MB #168363 +191)-Symbol (reserved=672MB +50MB, committed=672MB +50MB) (malloc=667MB +50MB #465680 +6396) (arena=5MB #1)-Native Memory Tracking (reserved=15MB, committed=15MB) (malloc=1MB #9771 +3390) (tracking overhead=15MB)-Unknown (reserved=20MB, committed=0MB) (mmap: reserved=20MB, committed=0MB)

可以看到其中的 Symbol 部分上涨明显,这部分主要是存储String intern等信息。所以可以初步判断是这部分泄露了。
寻找解决方案
  • 这里有个基础知识,即jdk8 对元空间的变化。可以自行google 查看变化。jdk8 之后永久代移除,元空间存放在native memory中。
  • 在搜NMT 内存泄露等关键字时发现,jdk似乎存在bug,会造成本地内存泄露:https://bugs.openjdk.java.net...
  • 可以看出表现基本一致:
【问题排查系列】JDK1.8 下内存不断增长排查及解决
文章图片

【问题排查系列】JDK1.8 下内存不断增长排查及解决
文章图片

  • 可以看到jdk1.8 131版本发现了这个问题,而我们使用的是101版本,所以怀疑也存在这个问题,于是尝试修改jdk版本来解决此问题。
  • 解决方案是升级jdk,于是升级到jdk1.8.0_202。
结果验证
  • 跟上一步骤一样,这次采用了对比的方式,即一台机器使用原始的jdk版本(101版本),另外一台使用202版本。经过2天的运行后,可以看到以下差距:
【问题排查系列】JDK1.8 下内存不断增长排查及解决
文章图片

  • 明显看出进行在申请内存方面的差异,而差异主要来源于Symbol。和jdk中的bug表现基本一致。
  • 也可以设置 -XX:MaxMetaspaceSize 对元空间进行限制,不过没有测试。因为目前内存泄露的主要原因还是bug,而不是过多的产生了大量的元数据或String interned。
整体排查思路
  • 使用jmap 查看内存情况,查看内存分配。
  • dump 堆快照分析堆内情况,排查内存泄露,注意dump会FGC,线上需下线进行。并且后来思考,如果是堆内存泄露其实不太会造成物理上的内存持续增长。因为堆的大小是确定的。
  • 堆内内存确认没有问题之后排查堆外内存,使用NMT进行排查,设置baseline,然后隔段时间进行比对。
  • 定位问题后查找解决方案,尝试解决。
  • 尝试解决后进行控制变量比对,确认问题真的解决。
  • 调整后线上稳定运行48H,确认调整没有带来其他副作用。
参考资料
  • Oracle 官方bug信息:https://bugs.java.com/bugdata...
  • JDK bug 信息:https://bugs.openjdk.java.net...
  • SymbolTable 存放的内容:https://blog.csdn.net/weixin_...
  • 同样使用NMT排查问题的另一示例:https://blog.csdn.net/qiansha...
  • PermGen与MetaSpace : https://segmentfault.com/a/11...
  • NMT 使用示例:https://blog.51cto.com/u_1512...

    推荐阅读