概述
通过对搜索集群中的内存不断增长问题的排查,总结排查内存方面的方法和经验。以便记录和参考。
问题表现
- 发布之后机器内存不断上涨。需要重启才能得到解决。
解决过程
jmap 排查堆内存阶段
- 使用传统的方式dump内存,然后使用Jprofiler或mat进行分析。
- 这里使用了对照的方式,即在重启后立即dump堆和运行一天后dump进行快照比对,发现差异较小,且使用了dump:live 和非live两种方式,均未发现堆内有明显异常。
详细操作方式这里不在赘述。
使用NMT排查堆外内存(native memory)
- 需要添加参数
-XX:NativeMemoryTracking=detail
据说添加了之后性能会下降 5% ~ 10% ,我添加了,没下降。不过还是建议线上慎重,添加一台机器排查问题即可。 - 设置NMT 的基线:
jcmd <pid> VM.native_memory baseline
设置基线之后即可标记一个基准内存状态,过一段时间之后可以比较内存的变化,哪部分增长的最多。 - 过一段时间后,使用
jcmd <pid> 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 131版本发现了这个问题,而我们使用的是101版本,所以怀疑也存在这个问题,于是尝试修改jdk版本来解决此问题。
- 解决方案是升级jdk,于是升级到jdk1.8.0_202。
结果验证
- 跟上一步骤一样,这次采用了对比的方式,即一台机器使用原始的jdk版本(101版本),另外一台使用202版本。经过2天的运行后,可以看到以下差距:
- 明显看出进行在申请内存方面的差异,而差异主要来源于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...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。