问题描述
线上的兜底 job 服务监控显示每天以 1% 的速度内存增长,直到 90% 以上,需要手动重启。防止频繁 gc 甚至 oom。
当前环境
jdk1.8.0_91
g1 垃圾回收器
排查过程
监控内存占用达到 60%,top 命令查看内存占用如下
然后查看 java 堆内存分布情况
查看堆内存占用正常。且远小于实际使用的 4.7g。
dump 堆内存用 mat 查看如下:
进一步确认是堆外内存泄漏。
堆外内存泄漏主要是以下几种原因
1.BIO中ByteBuffer分配的直接内存。-XX:MaxDirectMemorySize控制 主要在jvm非堆区
2.元空间 类信息 Symbotabel常量池 XX:MaxMetaspaceSize控制 主要在jvm非堆区
3.JNI或者JNA程序,直接操纵了本地内存,比如一些加密库、压缩解压等资源未释放。 在jvm外的区域
4. Stringtable 比如String.Intern方法调用 主要在jvm堆内堆区
使用 jdk 自带工具 jcmd 启动项目确认原因 项目启动时添加参数 会随时一定性能损耗
-XX:NativeMemoryTracking=detail 相关说明及用法可以参考 https://cloud.tencent.com/developer/article/1406522
可以看到前后对比
可以很明显看到主要是 Symbol 区 Stringtable 的增长 也就是 String.Intern 的调用。
查看相关代码 涉及到这个调用的是 Jackson 的反序列化为 map 的时候 对 key 做了这个操作,用来减少内存占用空间。
解决方案及总结:
** 这是一个 g1 StringTable::intern 的 bug **
https://bugs.openjdk.org/browse/JDK-8180048
http://mail.openjdk.java.net/pipermail/hotspot-gc-use/2017-February/002629.html
1. 禁用相关,重启后再进行观察,不再出现相关堆外内存泄漏。
new JsonFactory().disable(JsonFactory.Feature.INTERN_FIELD_NAMES);
2. 升级 JDK 到 192u 需要验证
3. 使用 cms 垃圾回收 需要验证
4. 升级相关 jackson 到 3.x 版本默认关闭 需要验证