java直接内存

[收起]
文章目录

  • java堆外内存泄漏排查案例

java堆外内存泄漏排查案例

我在Twitter的团队意外地建立了一个系统来检测激动人心的体育赛事:我们在三个不同的场合被寻呼,因为我们的一个服务在一个数据中心宕机。不用说,我们希望尽快取消这个系统的建设。我第一次被传呼是在西雅图海鹰队NFL季后赛的比赛中,在最后一分钟打成平手。我想其他的网页是由于NCAA篮球和板球世界杯。

这些事件导致大量用户同时发微博,发送流量大幅飙升。我们的服务最终使用了几GB的额外内存,并被Linux内核杀死。在一个实例被杀死后,其余实例的负载会增加,最终会将它们全部关闭。作为一个JVM服务,“额外”内存必须是native memory本机内存,而不仅仅是普通的Java对象分配,但是我们的服务没有对本机内存做任何特殊的处理。几周前,我们终于发现了潜在的Java本机内存泄漏。

始终关闭 gziputstreamgziputstream ,因为它们通过 zlib 使用本机内存。要追踪泄漏,请使用Jemalloc,并使用 MALLOC_CONF 环境变量启用采样分析。

与我之前提到的JVM mmap垃圾收集暂停类似,很多人花了很多时间调试这个问题,历时数月。每次我们被传呼时,我们都会设置额外的解决方法,以使将来更难触发。我们也花了更多的时间试图找到“真正的”错误,没有太多的成功。这个特殊的问题很容易重现:只需向一个测试实例发送许多并发请求。不幸的是,减慢实例的速度(例如使用strace)会使问题消失。既然我们的缓解措施似乎奏效了,我们就转向了更优先的问题。

几周前,有人报告了一个与我们类似的问题。kirandeeppaul建议使用jemalloc的内置分配配置文件,它跟踪调用malloc的内容。这是相对较低的成本,因为它只对malloc调用的一小部分进行采样。这也很容易尝试,因为可以通过设置MALLOC_CONF环境变量来打开它,而无需任何代码更改。这就成功了。

我的设置:

MALLOC_CONF=prof:true,lg_prof_interval:30,lg_prof_sample:17

它在每分配1 GB之后将配置文件写入磁盘,并每128 kB记录一次堆栈跟踪。可以使用jeprof(jemalloc的一部分)将此文件转换为图形。这为我们的服务创建了下图。

这表明 94% 的“活动”块是由Java_Java_util_zip_deflatter_init和deflatInit2(zlib的一部分)分配的,而只有4%来自os::malloc和JVM本身。

我发现这个程序压缩任何东西都很奇怪,所以我向Carsten Varming(Twitter-NYC的本地JVM专家)提到了它。他以前在Twitter上见过Deflater导致内存问题。它使用本机zlib库,该库使用malloc分配一个小缓冲区。如果它没有关闭,那么直到终结器运行时内存才会被释放,这可以解释这些症状。

我们使用linux perf来查找压缩内容的代码。在Twitter上,我们将JVM和perf配置为跨本机代码和Java代码生成堆栈跟踪,因此我运行perf record-g-p(PID)来记录堆栈跟踪,同时生成重载(您可以使用perf map agent来实现这一点)。

然后,我使用perf report搜索引用Deflater的堆栈,这很快就显示了 罪魁祸首 :一个异常记录器在压缩消息时没有关闭GZIPOutputStream。这解释了为什么它只发生在重载期间:只有在垃圾收集之间发生大量异常时,内存使用才会增加,最终终结器将运行以清除本机分配。添加 try/finally 块来关闭流解决了这个问题,并在负载下稳定了服务的内存使用。

我花了几个小时在Twitter上审核GZIP流的所有用法,发现有几个地方忘记关闭流。我不认为它们中的任何一个会导致类似的内存泄漏,但我还是修复了它们,以避免将来的复制和粘贴错误。(我在Apache Spark中也发现了一个)我认为FindBugs会报告这些问题的警告,这显示了静态分析的潜在价值,特别是对于这种“已知危险”的情况。我希望有一个预提交检查器阻止人们提交没有调用close()的代码使用。