cjuexuan / mynote

237 stars 34 forks source link

String intern造成的内存泄漏排查 #57

Open cjuexuan opened 6 years ago

cjuexuan commented 6 years ago

背景

最近遇到一个内存泄漏的case,这里分享下,这是一个老业务,最近代码没改,只是增加了对一个稍大的hbase集群的监控(大概4w个region,之前已经接过另一个小的hbase集群,跑了很久也没问题),在我们上线之后很快运维就找到我们,说我们这台机器用到了swap分区了,首先我们的Xmx是4G,没有oom,但是系统的res显示用到了50多g

实验

首先没oom的话,应该不是堆内存和direct buffer的泄漏,让运维dump了下,dump下的heap文件很小,不到1G,又打了个jstack,线程数也是比较正常的,那么应该是native的内存泄漏,这下就比较难排查了,网上google了下相关工具,google出的gperftools还可以,于是配置了下,然后打了个heap看了下,按照占比sort下

     0.0   0.0% 100.0%   5729.3  97.5% 0x00007fc84acf7fe5
     0.0   0.0% 100.0%   5732.5  97.5% Hashtable::new_entry
     0.0   0.0% 100.0%   5729.3  97.5% JVM_InternString
     0.0   0.0% 100.0%   5729.6  97.5% StringTable::basic_add
     0.0   0.0% 100.0%   5729.7  97.5% StringTable::intern@a24780
     0.0   0.0% 100.0%   5729.3  97.5% StringTable::intern@a24ca0
     0.0   0.0% 100.0%   5745.5  97.7% AllocateHeap
  5867.9  99.8%  99.8%   5867.9  99.8% os::malloc@9137e0

这里给出占比最高的部分,我们发现是StringTable的intern和hashtable的new_entry

分析

这里String的intern推荐阅读美团的tech blog

此时我们又找了下jdk的issue,看有没有和我们类似的问题,果然找到了两个链接

hotspot-gc-use jdk8 bugs

我们在这个应用上用的g1 gc,一直没有出现full gc,看到这个issue之后,我们就切回了cms,果然内存不再持续增长

进一步分析,我们这是一个http爬取hbase jmx的case,用到的框架比较少,内部框架主要调度相关的,另外用到了apache http client和jackson,我们自己肯定没有调用String intern的代码,搜索了下相关的api, 发现jackson中有一个特性,JsonFactory.Feature.INTERN_FIELD_NAMES,我们刚好用map接返回的数据,里面有很多region的hashcode,都是一些动态的key,感觉就被坑了一记了

总结

如果用map接收对象,并且有动态key,要disable INTERN_FIELD_NAMES,最好还是用对象接,不过我们这个case刚好不好用对象处理,而且被连环的问题触发导致了最终的结果

cjuexuan commented 6 years ago

这里贴一下我们最终发在公司博客上的文章

qihe777 commented 3 years ago

文章里面不是讲字符串是在堆内存创建的,常量池保存的是引用。为啥这里使用intern分配的内存是在堆外呀?