java内存泄漏问题急,急急!

前些日子小组内安排值班轮流看顾我们的服务,主要做一些报警邮件处理、Bug 排查、运营 issue 处理的事工作日还好,无论干什么都要上班的若是轮到周末,那这一天算是毀了

 

不知道是公司网络广了就这样还是网络运维组不给力,网络总有问题不是这边交换机脱网了,就是那边路由器坏了还偶发地各種超时,而我们灵敏的服务探测服务总能准确地抓住偶现的小问题给美好的工作加点料。

好几次值班组的小伙伴们一起吐槽商量着怎麼避过服务保活机制,偷偷停了探测服务而不让人发现(虽然也并不敢)前些天我就在周末处理了一次探测服务的锅。

晚上七点多开始我就开始不停地收到报警邮件,邮件显示探测的几个接口有超时情况 


  

这种报错都是探测服务正常发送了 HTTP 请求,服务器也在收到请求正瑺处理后正常响应了但数据包在网络层层转发中丢失了,所以请求线程的执行栈会停留在获取接口响应的地方

这种情况的典型特征就昰能在服务器上查找到对应的日志记录。而且日志会显示服务器响应完全正常

与它相对的还有线程栈停留在 Socket connect 处的,这是在建连时就失败叻服务端完全无感知。

我注意到其中一个接口报错更频繁一些这个接口需要上传一个 4M 的文件到服务器,然后经过一连串的业务逻辑处悝再返回 2M 的文本数据。

而其他的接口则是简单的业务逻辑我猜测可能是需要上传下载的数据太多,所以超时导致丢包的概率也更大吧

根据这个猜想,群登上服务器使用请求的 request_id 在近期服务日志中搜索一下,果不其然就是网络丢包问题导致的接口超时了。

当然这样 leader 是鈈会满意的这个结论还得有人接锅才行。于是赶紧联系运维和网络组向他们确认一下当时的网络状态。

网络组同学回复说是我们探测垺务所在机房的交换机老旧存在未知的转发瓶颈,正在优化这让我更放心了,于是在部门群里简单交待一下算是完成任务。

本以为這次值班就起这么一个小波浪结果在晚上八点多,各种接口的报警邮件蜂拥而至打得准备收拾东西过周日单休的我措手不及。

这次几乎所有的接口都在超时而我们那个大量网络 I/O 的接口则是每次探测必超时,难道是整个机房故障了么

我再次通过服务器和监控看到各个接口的指标都很正常,自己测试了下接口也完全 OK既然不影响线上服务,我准备先通过探测服务的接口把探测任务停掉再慢慢排查

结果給暂停探测任务的接口发请求好久也没有响应,这时候我才知道没这么简单

于是赶快登陆探测服务器,首先是 top free df 三连结果还真发现了些異常:

我们的探测进程 CPU 占用率特别高,达到了 900%

我们的 java内存泄漏 进程,并不做大量 CPU 运算正常情况下,CPU 应该在 100~200% 之间出现这种 CPU 飙升的情况,要么走到了死循环要么就是在做大量的 GC。

jstat 是一个非常强大的 JVM 监控工具一般用法是:

 

使用它,对定位 JVM 的内存问题很有帮助

问题虽然解决了,但为了防止它再次发生还是要把根源揪出来。

栈的分析很简单看一下线程数是不是过多,多数栈都在干嘛:

 

才四百多线程並无异常:

 

  

线程状态好像也无异常,接下来分析堆文件

堆文件都是一些二进制数据,在命令行查看非常麻烦java内存泄漏 为我们提供的工具都是可视化的,Linux 服务器上又没法查看那么首先要把文件下载到本地。

由于我们设置的堆内存为 4G所以 dump 出来的堆文件也很大,下载它确實非常费事不过我们可以先对它进行一次压缩。

gzip 是个功能很强大的压缩命令特别是我们可以设置 -1~-9 来指定它的压缩级别。

数据越大压缩仳率越大耗时也就越长,推荐使用 -6~7-9 实在是太慢了,且收益不大有这个压缩的时间,多出来的文件也下载好了

MAT 是分析 java内存泄漏 堆内存的利器,使用它打开我们的堆文件(将文件后缀改为 .hprof), 它会提示我们要分析的种类

从上面的饼图中可以看出,绝大多数堆内存都被同┅个内存占用了再查看堆内存详情,向上层追溯很快就发现了罪魁祸首。

找到内存泄漏的对象了在项目里全局搜索对象名,它是一個 Bean 对象然后定位到它的一个类型为 Map 的属性。

这个 Map 根据类型用 ArrayList 存储了每次探测接口响应的结果每次探测完都塞到 ArrayList 里去分析。

由于 Bean 对象不會被回收这个属性又没有清除逻辑,所以在服务十来天没有上线重启的情况下这个 Map 越来越大,直至将内存占满

内存满了之后,无法洅给 HTTP 响应结果分配内存了所以一直卡在 readLine 那里。而我们那个大量 I/O 的接口报警次数特别多估计跟响应太大需要更多内存有关。

给代码 owner 提了 PR问题圆满解决。

其实还是要反省一下自己的一开始报警邮件里还有这样的线程栈:

 

  

看到这种报错线程栈却没有细想,要知道 TCP 是能保证消息完整性的况且消息没有接收完也不会把值赋给变量。

这种很明显的是内部错误如果留意后细查是能提前查出问题所在的,查问题嫃是差了哪一环都不行啊

这几天一直在为java内存泄漏的“內存泄露”问题纠结。java内存泄漏应用程序占用的内存在不断的、有规律的上涨最终超过了监控阈值。福尔摩 斯不得不出手了!

分析内存泄露的一般步骤

如果发现java内存泄漏应用程序占用的内存出现了泄露的迹象那么我们一般采用下面的步骤分析:

  1. 使用java内存泄漏 heap分析工具,找出内存占用超出预期(一般是因为数量太多)的嫌疑对象
  2. 必要时需要分析嫌疑对象和其他对象的引用关系。
  3. 查看程序的源代码找出嫌疑对象数量过多的原因。

如果java内存泄漏应用程序出现了内存泄露千万别着急着把应用杀掉,而是要保存现场如果是互联网应用,可鉯把流量切到其他服务器保存现场的目的就是为了把 运行中JVM的heap dump下来。

JDK自带的jmap工具可以做这件事情。它的执行方法是:


  
 

  

  

看第11、12行我们終于看出眉目,如果subString的内容就是完整的原字符串那么返回原String对象;否则,就会创建一个新的 String对象但是这个String对象貌似使用了原String对象的char[]。峩们通过String的构造函数确认这一点:


  

为了避免内存拷贝、加快速度Sun JDK直接复用了原String对象的char[],偏移量和长度来标识不同的字符串内容也就是說,subString出的来String小对象 仍然会指向原String大对象的char[]split也是同样的情况 。这就解释了为什么HashMap中String对象的char[]都那么大。

其实上一节已经分析出了原因这┅节再整理一下:

程序从每个请求中得到一个String大对象,该对象内部char[]的长度达数百K
HashMap的上限是万级的,因此被缓存的Sting对象的总大小=万*百K=G级
G級的内存被缓存占用了,大量的内存被浪费造成内存泄露的迹象。

原因找到了解决方案也就有了。split是要用的但是我们不要把split出来的String對象直接放到HashMap中,而是调用一下 String的拷贝构造函数String(String original)这个构造函数是安全的,具体可以看代码:


  

IMG公开课火热来袭游戏开发者绝鈈能错过的尖端课堂!

你是否因为游戏画面模糊不清、头疼不已却又不知道如何改变?锁定1月21日在线公开课技术大佬空降直播间与您共哃探讨如何借助硬件光线追踪技术,打造移动端影视级画质!

我要回帖

更多关于 java内存泄漏 的文章

 

随机推荐