由来


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

不知道是公司网络广了就这样还是网络运维组不给力,网络总有问题,不是这边交换机脱网了就是那边路由器坏了,还偶发地各种超时,而我们灵敏地服务探测服务总能准确地抓住偶现的小问题,给美好的工作加点料。好几次值班组的小伙伴们一起吐槽,商量着怎么避过服务保活机制,偷偷停了探测服务而不让人发现(虽然也并不敢)。

前些天我就在周末处理了一次探测服务的锅。

转载随意,文章会持续修订,请注明来源地址:https://zhenbianshu.github.io 。

问题


网络问题?

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

java.io.BufferedReader.readLine(BufferedReader.java:371)
java.io.BufferedReader.readLine(BufferReader.java:389)
java_io_BufferedReader$readLine.call(Unknown Source)
com.domain.detect.http.HttpClient.getResponse(HttpClient.groovy:122)
com.domain.detect.http.HttpClient.this$2$getResponse(HttpClient.groovy)

这个线程栈的报错我见得多了,我们设置的 HTTP DNS 超时是 1s, connect 超时是 2s, read 超时是 3s,这种报错都是探测服务正常发送了 HTTP 请求,服务器也在收到请求正常处理后正常响应了,但数据包在网络层层转发中丢失了,所以请求线程的执行栈会停留在获取接口响应的地方。这种情况的典型特征就是能在服务器上查找到对应的日志记录。而且日志会显示服务器响应完全正常。 与它相对的还有线程栈停留在 Socket connect 处的,这是在建连时就失败了,服务端完全无感知。

我注意到其中一个接口报错更频繁一些,这个接口需要上传一个 4M 的文件到服务器,然后经过一连串的业务逻辑处理,再返回 2M 的文本数据,而其他的接口则是简单的业务逻辑,我猜测可能是需要上传下载的数据太多,所以超时导致丢包的概率也更大吧。

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

当然这样 leader 是不会满意的,这个结论还得有人接锅才行。于是赶紧联系运维和网络组,向他们确认一下当时的网络状态。网络组同学回复说是我们探测服务所在机房的交换机老旧,存在未知的转发瓶颈,正在优化,这让我更放心了,于是在部门群里简单交待一下,算是完成任务。

问题爆发

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

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

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

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

解决


内存泄漏

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

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

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

使用 jstat -gc pid [interval] 命令查看了 java 进程的 GC 状态,果然,FULL GC 达到了每秒一次。

这么多的 FULL GC,应该是内存泄漏没跑了,于是 使用 jstack pid > jstack.log 保存了线程栈的现场,使用 jmap -dump:format=b,file=heap.log pid 保存了堆现场,然后重启了探测服务,报警邮件终于停止了。

jstat

jstat 是一个非常强大的 JVM 监控工具,一般用法是: jstat [-options] pid interval

它支持的查看项有:

  • -class 查看类加载信息
  • -compile 编译统计信息
  • -gc 垃圾回收信息
  • -gcXXX 各区域 GC 的详细信息 如 -gcold

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

排查


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

分析栈

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

> grep 'java.lang.Thread.State' jstack.log  | wc -l
> 464

才四百多线程,并无异常。

> grep -A 1 'java.lang.Thread.State' jstack.log  | grep -v 'java.lang.Thread.State' | sort | uniq -c |sort -n

     10 	at java.lang.Class.forName0(Native Method)
10 at java.lang.Object.wait(Native Method)
16 at java.lang.ClassLoader.loadClass(ClassLoader.java:404)
44 at sun.nio.ch.EPollArrayWrapper.epollWait(Native Method)
344 at sun.misc.Unsafe.park(Native Method)

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

下载堆 dump 文件。

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

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

gzip 是个功能很强大的压缩命令,特别是我们可以设置 -1 ~ -9 来指定它的压缩级别,数据越大压缩比率越大,耗时也就越长,推荐使用 -6~7, -9 实在是太慢了,且收益不大,有这个压缩的时间,多出来的文件也下载好了。

使用 MAT 分析 jvm heap

MAT 是分析 Java 堆内存的利器,使用它打开我们的堆文件(将文件后缀改为 .hprof), 它会提示我们要分析的种类,对于这次分析,果断选择 memory leak suspect

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

分析代码

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

这个 Map 根据类型用 ArrayList 存储了每次探测接口响应的结果,每次探测完都塞到 ArrayList 里去分析,由于 Bean 对象不会被回收,这个属性又没有清除逻辑,所以在服务十来天没有上线重启的情况下,这个 Map 越来越大,直至将内存占满。

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

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

小结


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

groovy.json.internal.JsonParserCharArray.decodeValueInternal(JsonParserCharArray.java:166)
groovy.json.internal.JsonParserCharArray.decodeJsonObject(JsonParserCharArray.java:132)
groovy.json.internal.JsonParserCharArray.decodeValueInternal(JsonParserCharArray.java:186)
groovy.json.internal.JsonParserCharArray.decodeJsonObject(JsonParserCharArray.java:132)
groovy.json.internal.JsonParserCharArray.decodeValueInternal(JsonParserCharArray.java:186)

看到这种报错线程栈却没有细想,要知道 TCP 是能保证消息完整性的,况且消息没有接收完也不会把值赋给变量,这种很明显的是内部错误,如果留意后细查是能提前查出问题所在的,查问题真是差了哪一环都不行啊。

关于本文有什么疑问可以在下面留言交流,如果您觉得本文对您有帮助,欢迎关注我的 微博 或 GitHub 。您也可以在我的 博客REPO 右上角点击 Watch 并选择 Releases only 项来 订阅 我的博客,有新文章发布会第一时间通知您。

一次 Java 内存泄漏的排查的更多相关文章

  1. Java内存泄漏的排查总结

    Java内存泄漏的排查总结 https://blog.csdn.net/fishinhouse/article/details/80781673(缺图见下一条)内存泄漏的解决方案(转载)https:/ ...

  2. 一个java内存泄漏的排查案例

    这是个比较典型的java内存使用问题,定位过程也比较直接,但对新人还是有点参考价值的,所以就纪录了一下. 下面介绍一下在不了解系统代码的情况下,如何一步步分析和定位到具体代码的排查过程 (以便新人参考 ...

  3. 如何排查Java内存泄漏?看完我给跪了!

    没有经验的程序员经常认为Java的自动垃圾回收完全使他们免于担心内存管理.这是一个常见的误解:虽然垃圾收集器做得很好,但即使是最好的程序员也完全有可能成为严重破坏内存泄漏的牺牲品.让我解释一下. 当不 ...

  4. 一次完整的JVM堆外内存泄漏故障排查记录

    前言 记录一次线上JVM堆外内存泄漏问题的排查过程与思路,其中夹带一些JVM内存分配机制以及常用的JVM问题排查指令和工具分享,希望对大家有所帮助. 在整个排查过程中,我也走了不少弯路,但是在文章中我 ...

  5. java内存泄漏的几种情况

    转载于http://blog.csdn.net/wtt945482445/article/details/52483944 Java 内存分配策略 Java 程序运行时的内存分配策略有三种,分别是静态 ...

  6. java内存泄漏的定位与分析

    1.为什么会发生内存泄漏 java 如何检测内在泄漏呢?我们需要一些工具进行检测,并发现内存泄漏问题,不然很容易发生down机问题. 编写java程序最为方便的地方就是我们不需要管理内存的分配和释放, ...

  7. java内存泄漏

    java内存泄漏主要分成两个方面: (1)堆中申请的空间没有被释放 (2)对象已不在被使用,但是仍然存在在内存当中 以下集中情况可能会导致内存泄漏 (1)静态集合的使用hashmap和vector,静 ...

  8. Java内存泄漏分析与解决方案

    Java内存泄漏是每个Java程序员都会遇到的问题,程序在本地运行一切正常,可是布署到远端就会出现内存无限制的增长,最后系统瘫痪,那么如何最快最好的检测程序的稳定性,防止系统崩盘,作者用自已的亲身经历 ...

  9. (转)java内存泄漏的定位与分析

    转自:http://blog.csdn.net/x_i_y_u_e/article/details/51137492 1.为什么会发生内存泄漏 java 如何检测内在泄漏呢?我们需要一些工具进行检测, ...

随机推荐

  1. 51-迷宫(一)- java版dfs和bfs

    一天蒜头君掉进了一个迷宫里面,蒜头君想逃出去,可怜的蒜头君连迷宫是否有能逃出去的路都不知道. 看在蒜头君这么可怜的份上,就请聪明的你告诉蒜头君是否有可以逃出去的路. 输入格式 第一行输入两个整数 nn ...

  2. 使用xhprof对php7程序进行性能分析

    Xhprof是facebook开源出来的一个php轻量级的性能分析工具,跟Xdebug类似,但性能开销更低,还可以用在生产环境中,也可以由程序开关来控制是否进行profile. 对于还在使用php5的 ...

  3. go语言学习逻辑运算符if判断,iota的理解

    第一天学习go语言,首先吐槽一下,配置go语言浪费了我两个小时的时间 不是在百度,就是在百度的路上,这里介绍一下我的go语言的版本和开发平台 go语言1.12版本,之前没有用过在早的版本了首先记录一下 ...

  4. MySQL数据库开发常见问题及几点优化!

    从一下三个方面考虑: 库表设计 慢 SQL 问题 误操作.程序 bug 时怎么办 一.库表设计 1.1.引擎选择 在 MySQL5.1 中,引入了新的插件式存储引擎体系结构,允许将存储引擎加载到正在运 ...

  5. es5的语法学习

    1. strict模式 严格模式,限制一些用法,'use strict'; 2. Array增加方法 增加了every.some .forEach.filter .indexOf.lastIndexO ...

  6. EasyPR源码剖析(7):车牌判断之SVM

    前面的文章中我们主要介绍了车牌定位的相关技术,但是定位出来的相关区域可能并非是真实的车牌区域,EasyPR通过SVM支持向量机,一种机器学习算法来判定截取的图块是否是真的“车牌”,本节主要对相关的技术 ...

  7. 内核中的 ACCESS_ONCE()

    参考资料: https://blog.csdn.net/ganggexiongqi/article/details/24603363 这个真特么玄学了...

  8. eclipse配置逆向工程

    eclipse单行注释:ctrl + shfit + c   或者   Ctrl+/:    添加//注释 快速查找某个类:Ctrl+Shift +T   查找这个类的子类是ctrl+t eclips ...

  9. [gazebo-1] process has died [pid 22855, exit code 255,

    [gazebo-1] process has died [pid 22855, exit code 255, cmd /opt/ros/kinetic/lib/gazebo_ros/gzserver ...

  10. css3 --linear-gradient-渐变色

    //由上至下变色 background:-moz-linear-gradient( top,#f9b347,#f4ad40,#f9b347);  background:-webkit-gradient ...